2010-07-15 89 views
14

我正在用Perl写一个文件解析器,所以不得不循环遍历文件。文件由固定长度的记录组成,我想创建一个单独的函数来解析给定的记录并在循环中调用该函数。但是,最终的结果转变为大文件缓慢,我的猜测是我不应该使用外部函数。所以,我有和没有在一个循环中的函数调用进行了一些虚拟测试:为什么Perl中的函数调用循环如此缓慢?

[A]

foreach (1 .. 10000000) { 
$a = &get_string(); 
} 

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

[B]

foreach (1 .. 10000000) { 
$a = sprintf "%s\n", 'abc'; 
} 

测量表明,代码运行约3-4倍比代码B慢。我事先知道代码A应该运行得更慢,但是我仍然惊讶地发现差别很大。还试图用Python和Java运行类似的测试。在Python代码中,相当于比B慢20%,并且Java代码以相同的速度运行(如预期的那样)。从sprintf更改功能到别的没有显示任何显着差异。

有什么方法可以帮助Perl更快地运行这样的循环吗?我在这里做了些什么总是错误的,还是Perl的功能,函数调用是这样的开销?

+0

什么,准确地说,它get_string()呢? – eruciform 2010-07-15 12:34:24

+1

@roe我们假设这是一个存根,并且你没有使用'sprintf'只是将一个换行符粘贴到一个常量字符串上。那会很愚蠢。那么它究竟做了什么? – Schwern 2010-07-15 20:32:55

+0

奇怪,我的屏幕格式奇怪,它之前没有。 firefox goof .. – eruciform 2010-07-15 21:09:59

回答

8

您提出的问题与循环无关。在这方面,你的AB例子都是一样的。相反,问题在于直接的在线编码与通过函数调用相同的代码之间的区别。

函数调用确实涉及不可避免的开销。我不能给这些开销是否为什么是相对于其他语言的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); 

这里就是我上Perl v5.10.0(MSWin32-x86-多线程)。非常粗略地说,简单地调用一个什么都不做的函数就像直接运行我们的sprintf代码一样昂贵。

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

在一般情况下,如果你需要优化速度一些Perl代码,你就试着挤出效率的最后一滴,直接编码是要走的路 - 但往往是有代价的可维护性和可读性较差。但是,在开始进行这种微型优化的业务之前,您需要确保您的基础算法是稳定的,并且牢牢掌握代码的缓慢部分实际存在的位置。很容易浪费大量的精力来处理错误的事情。

+0

功能和功能之间只有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。 – Toto 2010-07-15 14:08:59

+0

@ M42我想你的意思是评论支石墓的答案。 – FMc 2010-07-15 14:21:06

+0

@FM:当然可以。 – Toto 2010-07-15 14:26:02

12

如果你的个子不带参数,是恒定的,如你的榜样,您可以在次声明中使用an empty prototype "()"获得重大加速:

sub get_string() { 
    return sprintf(“%s\n”, ‘abc’); 
} 

然而,这可能是一种特殊情况下你的例子与你的真实情况不符。这只是为了向您展示基准的危害。

您将通过阅读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); 

其结果是:

      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%     -- 
+3

常量文件夹在5.10.0和5.10.1之间显得更加智能。它曾经是Perl只能不断折叠非常简单的表达式。 5.10.1现在可以处理更复杂的事情,比如sprintf调用。 – Schwern 2010-07-15 19:14:44

+0

我的基准是在StrawberryPerl 5.12.0.1上。 – dolmen 2010-07-16 13:06:45

23

Perl函数调用是缓慢的。这很糟糕,因为你想要做的事情,将你的代码分解成可维护的功能,这是会减慢程序速度的。他们为什么慢?当Perl进入一个子程序时,Perl做了很多事情,结果它是非常动态的(即在运行时你可能会遇到很多事情)。它必须获得该名称的代码参考,检查它是代码参考,设置一个新的词法暂存器(用于存储my变量),一个新的动态范围(用于存储local变量),设置@_仅举几例,检查它被调用的上下文,并传递返回值。已经尝试优化这个过程,但他们没有付出。血腥的细节见pp_entersub in pp_hot.c

还有5.10.0中的一个bug减慢函数。如果您使用的是5.10.0,请升级。

因此,避免长时间循环重复调用函数。特别是如果它的嵌套。你可以缓存结果,也许使用Memoize?这项工作是否必须在循环内完成?它是否必须在最内圈循环内完成?例如:

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

header的通话可能被移出​​循环减少调用的次数从@things * @persons只是@things的。

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

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

perl优化器是恒定折叠样本代码中的sprintf调用。

可以deparse它,看它的发生:

$ 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 
相关问题