我在Perl中编写了一个文件解析器,因此不得不循环遍历文件.文件由固定长度的记录,我想作出这样的分析给定记录一个单独的函数,并调用该函数在一个循环.但是,对于大文件,最终结果变得缓慢,我的猜测是我不应该使用外部函数.所以我在循环中使用和不使用函数调用进行了一些虚拟测试:
[一个]
foreach (1 .. 10000000) {
$a = &get_string();
}
sub get_string {
return sprintf("%s\n", 'abc');
}
Run Code Online (Sandbox Code Playgroud)
[B]
foreach (1 .. 10000000) {
$a = sprintf "%s\n", 'abc';
}
Run Code Online (Sandbox Code Playgroud)
测量表明,代码运行比我事先知道代码中的本来运行速度变慢代码B.慢约3-4次,但我还是很惊讶,差别那么大.还试图用Python和Java运行类似的测试.在Python代码中的当量为除B慢约20%和Java代码被以相同的速度乳宁或多或少(如预期).将函数从sprintf更改为其他内容并未显示任何显着差异.
有没有办法帮助Perl更快地运行这样的循环?我在这里做了一些完全错误的事情,还是Perl的功能是函数调用是这样的开销?
Sch*_*ern 27
Perl函数调用很慢.它很糟糕,因为您想要做的事情,将代码分解为可维护的功能,这将会减慢您的程序速度.他们为什么慢?Perl在进入子程序时会做很多事情,因为它非常动态(即你可以在运行时搞乱很多东西).它必须得到该名称的代码参考,检查它是一个代码引用,设置一个新的词法暂存器(存储my变量),一个新的动态范围(存储local变量),设置@_为几个,检查什么它被调用的上下文并传递返回值.已经尝试优化这个过程,但他们没有付出代价.有关详细信息,请参见pp_entersubpp_hot.c.
此外,5.10.0中存在一个减慢功能的错误.如果您使用的是5.10.0,请升级.
因此,避免在长循环中反复调用函数.特别是如果它的嵌套.你可以使用Memoize缓存结果吗?是否必须在循环内完成工作?它是否必须在最内层循环中完成?例如:
for my $thing (@things) {
for my $person (@persons) {
print header($thing);
print message_for($person);
}
}
Run Code Online (Sandbox Code Playgroud)
可以将呼叫header移出@persons循环,从而将呼叫数量减少@things * @persons到刚好@things.
for my $thing (@things) {
my $header = header($thing);
for my $person (@persons) {
print $header;
print message_for($person);
}
}
Run Code Online (Sandbox Code Playgroud)
dol*_*men 14
如果你的sub没有参数并且在你的例子中是一个常量,你可以通过在子声明中使用空原型"()"来获得一个大的加速:
sub get_string() {
return sprintf(“%s\n”, ‘abc’);
}
Run Code Online (Sandbox Code Playgroud)
但是,这可能是您的示例的特殊情况,与您的实际情况不符.这只是为了向您展示基准测试的危险性.
通过阅读perlsub,您将学习这篇技巧和其他许多知识.
这是一个基准:
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);
Run Code Online (Sandbox Code Playgroud)
及其结果:
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% --
Run Code Online (Sandbox Code Playgroud)
你提出的问题与循环没有任何关系.在这方面,你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);
Run Code Online (Sandbox Code Playgroud)
这是我在Perl v5.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% --
Run Code Online (Sandbox Code Playgroud)
一般来说,如果你需要优化一些Perl代码以提高速度,并且你试图挤出最后一滴效率,那么直接编码是可行的方法 - 但这通常会带来较低的可维护性和可读性.但是,在开始进行这种微优化之前,您需要确保您的基础算法是可靠的,并且您已经牢牢掌握了代码的缓慢部分实际所在的位置.在错误的事情上浪费很多精力很容易.