Performance 为什么Perl循环中的函数调用如此缓慢?

Performance 为什么Perl循环中的函数调用如此缓慢?,performance,perl,Performance,Perl,我正在用Perl编写一个文件解析器,所以必须循环遍历文件。文件由固定长度的记录组成,我想创建一个单独的函数来解析给定的记录并在循环中调用该函数。然而,最终的结果是大文件速度慢,我猜我不应该使用外部函数。因此,我在循环中进行了一些有函数调用和无函数调用的虚拟测试: [A] [乙] 测量表明,代码A的运行速度大约是代码B的3-4倍。我事先就知道代码A应该运行得更慢,但我仍然对差异如此之大感到惊讶。还尝试使用Python和Java运行类似的测试。在Python代码中,A的速度比B慢20%,Java代码

我正在用Perl编写一个文件解析器,所以必须循环遍历文件。文件由固定长度的记录组成,我想创建一个单独的函数来解析给定的记录并在循环中调用该函数。然而,最终的结果是大文件速度慢,我猜我不应该使用外部函数。因此,我在循环中进行了一些有函数调用和无函数调用的虚拟测试:

[A]

[乙]

测量表明,代码A的运行速度大约是代码B的3-4倍。我事先就知道代码A应该运行得更慢,但我仍然对差异如此之大感到惊讶。还尝试使用Python和Java运行类似的测试。在Python代码中,A的速度比B慢20%,Java代码的运行速度大致相同(正如预期的那样)。将函数从sprintf更改为其他函数并没有显示出任何显著差异


有没有办法帮助Perl更快地运行这样的循环?我在这里做的是完全错误的事情,还是Perl的特性导致函数调用如此高的开销?

您提出的问题与循环无关。您的
A
B
示例在这方面是相同的。相反,问题在于直接、在线编码与通过函数调用相同代码之间的区别

函数调用确实涉及不可避免的开销。对于Perl中的这种开销是否以及为什么比其他语言更昂贵的问题,我无法回答,但我可以提供一个更好的方法来衡量这类事情:

use strict;
use warnings;
use Benchmark qw(cmpthese);

sub just_return { return }
sub get_string  { my $s = sprintf "%s\n", 'abc' }

my %methods = (
    direct      => sub { my $s = sprintf "%s\n", 'abc' },
    function    => sub { my $s = get_string()          },
    just_return => sub { my $s = just_return()         },
);

cmpthese(-2, \%methods);
以下是我在PerlV5.10.0(MSWin32-x86-multi-thread)上得到的信息。非常粗略地说,简单地调用一个不做任何事情的函数与直接运行我们的
sprintf
代码一样昂贵

                 Rate    function just_return      direct
function    1062833/s          --        -70%        -71%
just_return 3566639/s        236%          --         -2%
direct      3629492/s        241%          2%          --

一般来说,如果您需要优化一些Perl代码以提高速度,并试图挤出每一点效率,那么直接编码是一种方法——但这通常会带来维护性和可读性较差的代价。然而,在您进入这种微观优化业务之前,您需要确保您的底层算法是可靠的,并且您已经牢牢掌握了代码中缓慢部分的实际位置。很容易将大量精力浪费在错误的事情上。

如果您的sub没有参数并且是一个常量,如您的示例所示,您可以通过在sub声明中使用以下命令来获得较大的加速:

sub get_string() {
    return sprintf(“%s\n”, ‘abc’);
}
然而,这可能是您的示例的一个特殊情况,与您的实际情况不匹配。这只是向您展示基准测试的危险性

通过阅读,您将了解此技巧和其他许多技巧

以下是一个基准:

use strict;
use warnings;
use Benchmark qw(cmpthese);

sub just_return { return }
sub get_string  { sprintf "%s\n", 'abc' }
sub get_string_with_proto()  { sprintf "%s\n", 'abc' }

my %methods = (
    direct      => sub { my $s = sprintf "%s\n", 'abc' },
    function    => sub { my $s = get_string()          },
    just_return => sub { my $s = just_return()         },
    function_with_proto => sub { my $s = get_string_with_proto() },
);

cmpthese(-2, \%methods);
其结果是:

                          Rate function just_return   direct function_with_proto
function             1488987/s       --        -65%     -90%                -90%
just_return          4285454/s     188%          --     -70%                -71%
direct              14210565/s     854%        232%       --                 -5%
function_with_proto 15018312/s     909%        250%       6%                  --

Perl函数调用很慢。这很糟糕,因为你想做的事情,将代码分解成可维护的函数,正是这件事会让你的程序慢下来。他们为什么慢?Perl在进入子例程时会做很多事情,这是因为它是非常动态的(即,在运行时可以处理很多事情)。它必须获取该名称的代码引用,检查它是否是代码引用,设置新的词汇草稿行(存储
my
变量),新的动态范围(存储
local
变量),设置
@
来命名一些,检查调用它的上下文并传递返回值。有人试图优化这一过程,但没有取得成效。有关血淋淋的详细信息,请参阅

此外,5.10.0中还有一个减慢函数速度的错误。如果您使用的是5.10.0,请升级

因此,避免在长循环中反复调用函数。特别是如果它是嵌套的。您是否可以缓存结果,或者使用?工作必须在循环内完成吗?是否必须在最内部的循环内完成?例如:

for my $thing (@things) {
    for my $person (@persons) {
        print header($thing);
        print message_for($person);
    }
}
可以将对
标题的调用移出
@persons
循环,从而将调用次数从
@things*@persons
减少到只调用
@things

for my $thing (@things) {
    my $header = header($thing);

    for my $person (@persons) {
        print $header;
        print message_for($person);
    }
}

perl优化器不断地折叠示例代码中的
sprintf
调用

您可以将其拆下以查看其发生情况:

$ perl -MO=Deparse sample.pl
foreach $_ (1 .. 10000000) {
    $a = &get_string();
}
sub get_string {
    return "abc\n";
}
foreach $_ (1 .. 10000000) {
    $a = "abc\n";
}
- syntax OK

get_string()到底做了什么?@roe我们假设这是一个存根,而您并没有使用
sprintf
将换行符粘贴到一个常量字符串上。那太傻了。那它到底是做什么的呢?奇怪,我的屏幕格式奇怪,它以前不在那里。firefox goof..sprintf只是用于测试。在实际情况中,函数是将应用substr的记录(ASCII字符串)拆分几次(在这种情况下,解包速度较慢)。速度上的差异基本相同。我得到的函数和proto函数之间的差异只有1%或2%。Perl 5.10/windows XP和Perl 5.8.5 i386/Linux 2.6.12 i386和Perl 5.8.8 x86_64/Linux 2.6.18 x86_64。@M42我想你是想对dolmen的答案发表评论。注意:5.10.0有一个错误,严重减慢了函数调用。试试5.10.1。@Schwern谢谢,很高兴知道。在5.10.0和5.10.1之间,常量文件夹似乎变得更智能了。过去,Perl只能对非常简单的表达式进行常量折叠。5.10.1现在可以处理更复杂的事情,比如sprintf调用;也许permalink会更好?例如。(我没有将它编辑到你的答案中,因为我不知道你的观点是否仍然存在于这个版本的
pp_entresub
,这个版本比你的答案更新了9年)@Dada我将链接到该文件,让用户搜索。我的观点仍然成立,Perl中的函数调用仍然很慢,我相信这是由于调用函数和改变Perl中作用域的复杂性造成的。
for my $thing (@things) {
    my $header = header($thing);

    for my $person (@persons) {
        print $header;
        print message_for($person);
    }
}
$ perl -MO=Deparse sample.pl
foreach $_ (1 .. 10000000) {
    $a = &get_string();
}
sub get_string {
    return "abc\n";
}
foreach $_ (1 .. 10000000) {
    $a = "abc\n";
}
- syntax OK