9

我一直在对我用 Perl 编写的框架的性能进行基准测试,与我们现有的代码库相比,每秒请求数减少了 50%(有些命中是可以理解的,因为我们正在从程序化的意大利面条代码变为OOP MVC 框架)。

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

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

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

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

目前每个项目都有一个 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});

仅在编译时执行此操作,我的引导程序和项目基础模块都必须更改(我认为),但我非常不确定要进行哪些更改并且仍然保持代码的美观和精简。谁能在这里指出我正确的方向?

更新

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

package MyApp::bootstrap;
use MyApp;

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

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

仅这一快速更改就让我每秒的请求数增加了50% ,这证实了我的想法,即配置文件是一个值得修复的主要瓶颈。我们古怪的旧开发机器上的基准数字是 60rps,仅此更改,我的框架就从 30rps 变为 45rps。对于那些说 Moose 很慢并且编译时间很短的人。在启动时编译所有 Moose 代码时,我得到了与预编译我的配置文件时相同(50%)的增长。

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

4

6 回答 6

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 中编译。

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

于 2008-12-04T23:55:28.860 回答
4

如果你可以让你的 Moose 类不可变,那可能会给你另一个减速带。

于 2008-12-05T13:13:51.007 回答
3

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

在下面的示例中,我们使用导入方法来读取具有给定参数的配置文件,然后将该配置推送到调用包中。

警告是调用包中的任何$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'; 
于 2009-07-16T16:14:29.413 回答
1

我在 HTML::Mason 框架安装中遇到了同样的问题,并且发现它工作得很好:在 httpd.conf 中:

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

在您的 handler.pl 文件中,您定义了所有静态项目,例如配置、数据库句柄等。这将它们定义在 YourModule::Mason 的范围内,该范围是在 apache 线程启动时编译的(新线程显然具有固有的高架)。YourModule::Mason 然后有一个handler处理请求的方法。

我承认 HTML::Mason 中可能发生了一些神奇的事情,可以帮助我解决这个问题,但它对我有用,也许对你有用?

于 2008-12-05T00:54:33.653 回答
0

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

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

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

但请注意陷阱:一种简单的闯入方法

于 2008-12-22T15:31:09.973 回答
-2

JackM 的想法是正确的。

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

这确实是 mod_perl 和 CGI​​ 之间差异的另一半。前半部分是 mod_perl 的持久 perl 引擎与 CGI 每次调用的重生 perl。

于 2008-12-05T13:24:56.650 回答