在使用mod_perl时,如何仅在编译时执行低效代码?

时间:2008-12-04 23:42:30

标签: performance perl mod-perl

我一直在对我在Perl中编写的框架的性能进行基准测试,并且我的每秒请求数比现有代码库减少了50%(有些命中是可以理解的,因为我们将从程序意义上来看代码到OOP MVC框架)。

应用程序在mod_perl下运行,我已将Moose和我的所有框架代码添加到startup.pl script中,而Config::General本身的请求每秒加倍。我希望进一步增强这个数字,使其尽可能接近现有数量。我们认为这是过早的优化,但是我想解决一些明显的低效问题,看看它是如何影响性能的。

与大多数框架一样,我有一个配置文件和一个调度程序。配置部分由{{3}}处理,因此需要一些IO和解析来将我的配置文件加载到应用程序中。我在这里看到的最大问题是我正在为每一个请求进行此操作!

在我的应用程序上运行Devel :: Dprof指向Config :: General :: BEGIN和一堆相关的IO模块作为不是Moose的主要慢点之一。所以我想要做的事情,以及在后见之明更有意义的是利用mod_perl的持久性和startup.pl编译的东西,只需要在配置文件中加载一次 - 当服务器启动时。 / p>

问题是我不太熟悉它是如何工作的。

目前每个项目都有一个非常精简的PerlHandler引导类,如下所示:

use MyApp; 
MyApp->new(config_file => '/path/to/site.config')->run();

MyApp.pm继承自框架Project模块,该模块具有以下代码:

my $config = Config::General->new(
                -ConfigFile => $self->config_file,
                -InterPolateVars => 1,
             );    

$self->config({$config->getall});

要仅在编译时执行此操作,我的引导程序和Project基础模块都必须更改(我认为),但我不确定要进行哪些更改并仍然保持代码良好和精益。任何人都能指出我在正确的方向吗?

更新

我在ysth的回答中尝试了每个项目模块方法中的BEGIN BLOCK。所以我现在有:

package MyApp::bootstrap;
use MyApp;

my $config;
BEGIN
{
    $config = {Config::General->new(...)->getall};        
}

sub handler { ..etc.
    MyApp->new(config => $config)->run();

这一快速变化让我每秒的请求增加了 50%,这证实我认为配置文件是一个值得修复的主要瓶颈。我们的crotchety旧开发机器上的基准数字是60rps,我的框架已经从30rps变为45rps单独进行此更改。对于那些说Moose很慢并且编译时间很短的人来说......在启动时编译我所有的Moose代码时我得到了相同的(50%)增加,就像我预先编译我的配置文件一样。

我现在唯一的问题是这违反了DRY原则,因为每个BEGIN块中的相同Config :: General->新代码只有配置文件的路径不同。我有一些不同的策略来限制这个,但我只是想发布这个变化的结果。

6 个答案:

答案 0 :(得分:10)

假设您的应用程序根本没有更改配置,请将其移至开始块:

# this code goes at file scope
my $config;
BEGIN {
    $config = { Config::General->new( ... )->getall }
}

# when creating a new instance
$self->config( $config );

确保所有模块都在startup.pl中编译。

你可以变得更加漂亮,并且让单例类提供配置哈希, 但你不需要。

答案 1 :(得分:4)

如果你可以制作你的驼鹿课程immutable,那可能会给你另一个减速带。

答案 2 :(得分:3)

模块的import sub在编译时执行,因此我们可以使用它来减少/消除ysth's answer的DRY。

在下面的示例中,我们使用import方法读取配置文件,其中包含给我们的参数,然后将该配置推送到调用包中。

警告是调用包中的任何$config变量将被此消除。

package Foo_Config;
use English qw(-no_match_vars);
sub import {
   my ($self, @cfg) = @ARG;
   my $call_pkg     = caller;
   my $config       = {Config::General->new(@cfg)->getall};
   do{ # this will create the $config variable in the calling package.
       no strict 'refs';
       ${$call_pkg . '::config'} = $config;
   };
   return;
}

package MyApp;
# will execute Foo_Config->import('/path/to/site.config') at compile time.
use Foo_Config '/path/to/site.config'; 

答案 3 :(得分:1)

我在HTML :: Mason框架安装中遇到了同样的问题,发现这个问题工作得相当好: 在httpd.conf中:

PerlRequire handler.pl
<FilesMatch "\.mhtml$">
  SetHandler perl-script
  PerlHandler YourModule::Mason
</FilesMatch>

在你的handler.pl文件中,你定义了所有的静态项,比如你的配置,数据库句柄等。这在默认线程启动时编译的YourModule :: Mason的范围内定义它们(新的线程显然会有固有的开销)。然后,YourModule :: Mason有一个处理请求的handler方法。

我会承认HTML :: Mason中可能会有一些神奇的东西正在帮我解决这个问题,但它对我有用,也许适合你?

答案 4 :(得分:0)

通过少量更改来加速这些事情的常见方法是在同一Apache进程的调用之间简单地使用全局变量和缓存状态:

use vars qw ($config);
# ...
$config = Config::General->new( ... )->getall
    unless blessed($config); # add more suitable test here

它不是很干净并且可能导致模糊的错误(尽管“我的$ var”导致我的经验更多)并且它有时会占用大量内存,但是可以通过这种方式避免许多(重复的)昂贵的初始化语句。使用BEGIN {}的优势;代码只是您可以根据其他事件重新初始化,而无需重新启动apache或终止进程(例如,通过在上面的测试中将文件的时间戳包含在磁盘上)。

注意陷阱:an easy way to break in

答案 5 :(得分:-2)

JackM有正确的想法。

通过加载所有类并在“ Mother ”Apache进程中实例化您的应用程序级对象(在您的情况下,配置),您不必每次都编译它们新工人产生,因为他们已经可以在内存中存在。我们非常细致,为他们的应用程序定期使用的每个模块添加“使用”行。如果您不在母船中加载软件包和模块,每个工作人员不仅会受到加载模块的性能影响,还无法获得现代操作系统提供的内存共享的好处。

这是mod_perl和CGI之间差异的另一半。上半部分是每次调用时mod_perl的持久性perl-engine和CGI的重生perl。