Performance 为什么Perl循环中的函数调用如此缓慢?
我正在用Perl编写一个文件解析器,所以必须循环遍历文件。文件由固定长度的记录组成,我想创建一个单独的函数来解析给定的记录并在循环中调用该函数。然而,最终的结果是大文件速度慢,我猜我不应该使用外部函数。因此,我在循环中进行了一些有函数调用和无函数调用的虚拟测试: [A] [乙] 测量表明,代码A的运行速度大约是代码B的3-4倍。我事先就知道代码A应该运行得更慢,但我仍然对差异如此之大感到惊讶。还尝试使用Python和Java运行类似的测试。在Python代码中,A的速度比B慢20%,Java代码的运行速度大致相同(正如预期的那样)。将函数从sprintf更改为其他函数并没有显示出任何显著差异Performance 为什么Perl循环中的函数调用如此缓慢?,performance,perl,Performance,Perl,我正在用Perl编写一个文件解析器,所以必须循环遍历文件。文件由固定长度的记录组成,我想创建一个单独的函数来解析给定的记录并在循环中调用该函数。然而,最终的结果是大文件速度慢,我猜我不应该使用外部函数。因此,我在循环中进行了一些有函数调用和无函数调用的虚拟测试: [A] [乙] 测量表明,代码A的运行速度大约是代码B的3-4倍。我事先就知道代码A应该运行得更慢,但我仍然对差异如此之大感到惊讶。还尝试使用Python和Java运行类似的测试。在Python代码中,A的速度比B慢20%,Java代码
有没有办法帮助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