在Perl中,为什么绑定数组这么慢?

时间:2011-04-01 19:58:36

标签: perl performance

在我的测试中,我注意到迭代绑定数组的速度最好是使用内部访问器方法(FETCHFETCHSIZE)的一半。以下基准显示了该问题:

{package Array;
    sub new {
        my $class = shift;
        tie my @array, $class, [@_];
        \@array
    }
    sub TIEARRAY {
        my ($class, $self) = @_;
        bless $self => $class;
    }
    sub FETCH     {$_[0][$_[1]]}
    sub FETCHSIZE {scalar @{$_[0]}}
}

use List::Util 'sum';
use Benchmark 'cmpthese';

for my $mag (map 10**$_ => 1 .. 5) {

    my $array = Array->new(1 .. $mag);
    my $tied  = tied(@$array);
    my $sum   = sum @$array;

    print "$mag: \n";
    cmpthese -2 => {
        tied => sub {
            my $x = 0;
            $x += $_ for @$array;
            $x == $sum or die $x
        },
        method => sub {
            my $x = 0;
            $x += $tied->FETCH($_) for 0 .. $tied->FETCHSIZE - 1;
            $x == $sum or die $x
        },
        method_while => sub {
            my $x = 0;
            my $i = 0; $x += $tied->FETCH($i++) while $i < $tied->FETCHSIZE;
            $x == $sum or die $x
        },
        method_while2 => sub {
            my $x = 0;
            my $i = 0;
            $x += tied(@$array)->FETCH($i++) 
                while $i < tied(@$array)->FETCHSIZE;
            $x == $sum or die $x
        },
        method_while3 => sub {
            my $x = 0;
            my $i = 0;
            while ($i < tied(@$array)->FETCHSIZE) {
                local *_ = \(tied(@$array)->FETCH($i++));
                $x += $_
            }
            $x == $sum or die $x
        },
    };
    print "\n";
}

如果数组大小为1000,则基准返回:

1000: 
                Rate   tied method_while3 method_while2 method_while   method
tied           439/s     --          -40%          -51%         -61%     -79%
method_while3  728/s    66%            --          -19%         -35%     -65%
method_while2  900/s   105%           24%            --         -19%     -57%
method_while  1114/s   154%           53%           24%           --     -47%
method        2088/s   375%          187%          132%          87%       --

我省略了其他的运行,因为数组的大小不会对相对速度产生有意义的变化。

method当然是最快的,因为它不会在每次迭代时检查数组的大小,但method_whilemethod_while2似乎在绑定数组中运行与for循环相同的方式,即使较慢的method_while2也是绑定数组的两倍。

即使在$_中向method_while2添加method_while3和别名分配的本地化,也会比绑定数组的执行速度快66%。

for中未发生的method_while3循环中发生了哪些额外的工作?有没有办法提高绑定数组的速度?

3 个答案:

答案 0 :(得分:7)

每次使用绑定数组时,都必须查找绑定对象,然后查找方法,然后调用它们。对于其他版本,您在编译时或在循环之前执行部分或全部查找,而不是在每次访问时执行。

method和其他method_*版本之间的速度比较就是一个很好的例子,顺便说一下:你看到了这样做的费用FETCHSIZE,甚至已经查找了绑定对象。现在将该成本应用于触及阵列的每个操作。)

答案 1 :(得分:3)

在基准测试中,你做了

... for @$array;

如果你做了什么

++$_ for @$array;

它会起作用。当在左值上下文中返回值时,Tie magic将FETCH返回的值包装到左值中。你可以使用Devel :: Peek来看到这个。

use Devel::Peek;
Dump( $array->[2] );

SV = PVLV(0x14b2234) at 0x187b374
  REFCNT = 1
  FLAGS = (TEMP,GMG,SMG,RMG)
  IV = 0
  NV = 0
  PV = 0
  MAGIC = 0x14d6274
    MG_VIRTUAL = &PL_vtbl_packelem
    MG_TYPE = PERL_MAGIC_tiedelem(p)
    MG_FLAGS = 0x02
      REFCOUNTED
    MG_OBJ = 0x14a7e5c
    SV = IV(0x14a7e58) at 0x14a7e5c
      REFCNT = 2
      FLAGS = (ROK)
      RV = 0x187b324
      SV = PVAV(0x187c37c) at 0x187b324
        REFCNT = 1
        FLAGS = (OBJECT)
        STASH = 0x14a842c       "Array"
        ARRAY = 0x0
        FILL = -1
        MAX = -1
        ARYLEN = 0x0
        FLAGS = (REAL)
    MG_LEN = 2
  TYPE = t
  TARGOFF = 0
  TARGLEN = 0
  TARG = 0x187b374

将FETCH返回的值包装成一个神奇的SV并处理 魔法至少解决了一些差异。

tied_rvalue => sub {
    my $x = 0;
    $x += $_ for @$array;
    $x == $sum or die $x
},
tied_lvalue => sub {
    my $x = 0;
    $x += $array->[$_] for 0 .. $tied->FETCHSIZE - 1;
    $x == $sum or die $x
},

100:
                 Rate tied_rvalue method_while3 tied_lvalue method_while2 method_while method
tied_rvalue    3333/s          --          -33%        -36%          -50%         -58%   -77%
method_while3  4998/s         50%            --         -4%          -25%         -36%   -66%
tied_lvalue    5184/s         56%            4%          --          -23%         -34%   -65%
method_while2  6699/s        101%           34%         29%            --         -15%   -55%
method_while   7856/s        136%           57%         52%           17%           --   -47%
method        14747/s        342%          195%        184%          120%          88%     --

答案 2 :(得分:3)

值得注意的是local非常慢,这导致method_while3与其他方法基准测试中的一些性能损失。它也在进行块进入和退出,这需要花费。

尽管method_while3在编程上等同于statement for x..y,但perl可以更好地优化for循环。根据经验,你在perl中执行的代码越多,代码就会越快。