Perl CGI的性能

/ns/wz/net/data/20040418172947.htm

摘自:《Perl Web开发技术》(翻译版),机械工业出版社,王爱过,周丽萍 等
('Perl for the Web',Chris Radcliff)

Perl CGI可能在开发网站的初期是一种常用方式。但在实际使用后采用Perl CGI,性能问题就开始变得很明显。这种情况在原型应用程序应用在实际环境中经常碰到,表现为较慢的响应速度、不寻常的行为和系统失败等。在一个站点遭遇Perl CGI脚本产生的性能问题之后,不可能再次允许其出现在实际环境中。
Perl CGI在测试一个Web程序的初期工作很好,但随着站点使用得越来越多,应用程序负载也更大,CGI进程产生越来越多的开销,并最终超过服务器的能力。处理器过载,内存被占满,数据库没有可用连接,系统将比预期地更早终止。
Perl 的随机编译器在这里是一个最大缺陷,执行Perl CGI进程时CPU的许多负载都来源于编译和初始化程序。最明显的结论是Perl相对当前任务响应太慢了,当然不会有任何公司会鼓吹Perl CGI的速度,在实际使用中,经过优化的Perl程序的例子更少。

============
一次性任务
============
Perl CGI进程在原型测试环境下工作得很好,这时通常每次只有一个用户访问Web应用程序,并且所有界面延迟时间只是由于用户请求的处理引起的。这与Perl程序在Web处理之外的其他情况下的使用方式很类似,所以,那些情况的假设在此都成立,也就是每次只有一个请求,在请求完成后,程序和数据就不再需要了。
这些假定在演示原型时也是正确的。这时演示者是访问系统的唯一用户,通常处理过程产生的延迟很小并且也被解释过程给掩盖了。结果,CGI进程响应的缓慢性不可能在演示或可用性测试阶段觉察出来。即使单一用户注意到运行缓慢,也可能注重其他考虑而被忽视。
把原型投入实际使用就会截然不同。关于一个用户、很少的请求和没有对资源竞争的假设不再成立,应用程序的负载会按指数规律增长,这是由于Web请求的性质,他们不像单一用户应用程序中那样在同意时刻输入,也不像大多数用户应用程序(如数据库或群件)那样固定在一定范围之内。Web请求可能同时输入,并且数量可能不断增加,而不采取定单处理过程中排队请求那样简单的方式。相反,要求Web服务器存在和请求一样多的进程,附加进程的负载似的现有进程更加缓慢,因此,在处理以前进程的时候,会有更多的进程输入进来。在有限负载下,这种情况很容易失控,从而导致所有的Perl CGI进程阻塞停止,在处理过程中的所有请求都失败了。

====================
Perl进程的内存痕迹
====================
一个Perl CGI进程可能占用1MB到15MB的RAM空间。占用这些内存的进程如果单独处理很容易管理,甚至不能使最一般的Web服务器崩溃。然而,CGI进程处理过程中使用的Perl编译器可能是静态链接而不是动态链接,导致一个不在进程间共乡的Perl解释器。如果许多CGI进程在同时运行,Perl解释器就可能复制无数次,将占用更多的内存开销。
如果新进程在旧进程没结束之前就开始了,新进程会站用更多的系统内存。随着站点的经常光顾的访问者的增多,将会产生更多的覆盖和系统资源的更大消耗。例如,如果一个站点的一个CGI应用程序每秒接到20个请求,那么最少需要2秒钟来处理,可能要40个CGI进程不停地开始和结束,这样会占用500M的RAM空间,随着请求率的增加,对一个实际的Web应用程序来说,100个请求每秒仍是一个非常保守的数字,内存请求增加不是只简单相加,而是呈几何基数地增加。每个进程通过增加CPU负载从而减慢其他进程,但这样又会增加必须的进程数,并且会恶性循环下去。这样即使用最昂贵的Web服务器,可用RAM的限制也将很快突破。
当可用RAM被CGI进程需求超过后,大多数服务器开始使用磁盘上的交换空间来为进程产生虚拟内存。这时,进程开始明显变得缓慢,处理一个进程的时间成10倍地增加,同时,活动进程的数量也同样增加了。进程总负载一旦超过系统的极限,所有进程都会终止。同样,请求失败,连接的系统可能会处在不确定的状态下,并产色怀念感不寻常的行为和管理上的混乱。

============
编译的费用
============
Perl CGI的大部分开销来自于编译成字节码和初始化程序数据。每个Perl程序在执行前都要经过编译,每个程序(不管采用什么语言)都需要出事内存结构和运行时系统库函数。编译过程对任何在系统环境中不许要解释器就运行的程序都是必须的。
编译过程在运行一个单用户程序时很少能看得到,如 C 程序,编译过程需要几分钟甚至几小时。编译步骤也是检查系统相关错误的时候,所以如果第一个编译步骤没成功,就需要更多的调试,也就需要更多的时间。因此,编译过程通常在程序提交给用户之前就已完成了。即使开放源代码程序通常也只在目标系统上第一次安装时编译一次。
而Perl程序要在执行前进行编译,这使Perl程序可以更迅速地对系统环境变化做出响应,也比编译过的执行程序更容易跨平台移植。Perl runtime在几秒(或几毫秒)内编译每个程序,包括所有库函数和核心程序。Perl程序开始时同时开始编译和执行步骤,这就给人Perl是解释不是编译的印象。而Perl runtime(有时叫Perl解释器)需要在编译和执行每个程序时激活,这种印象也就更深了。
对有些Perl程序,编译时间不止几秒钟。程序更大时更是如此,因此要添加大量的模块到程序中。初始变量、系统连接和更大的数据结构都可能增加系统初始阶段的时间开销。例如,把较大的XML文档分析Perl数据结构,可能会占用相当的时间,这些额外的编译和初始时间造成了PerlCGI使用范围的限制,因为更复杂的过程可能会包含不允许的性能损失。
除了大多数集中处理器操作的Perl程序,编译一个Perl程序会比简单执行编译过程的程序占用更多的CPU时间,因为编译程序需要于Perl语言本身同样的文本操作、系统集成和磁盘输入/输出,但更复杂一些。实际在大多数情况下,一个Perl CGI进程占用比相同的经过预编译的程序多20倍的CPU处理器时间。

======
小结
======
建立Web应用程序的原型是Perl的用武之地,许多Perl程序开始是作为Perl原型的。Perl的灵活性和强大的系统集成模块使原型设计和实施特别容易。Perl CGI的性能在短时间内受到挫折,并且Perl CGI进程占用更多内存和CPU时间,最后可能使系统过载,完全终止Web服务器进程。这主要是由于Perl进程的实时编译的开销引起的,虽然是一、两秒钟,但这比程序本身还需要更多的处理能力。

=========================
文章类型:转载 提交:Aoming 核查:NetDemon