在我的测试中,我注意到迭代绑定数组的速度最好是使用内部访问器方法(FETCH
和FETCHSIZE
)的一半。以下基准显示了该问题:
{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_while
和method_while2
似乎在绑定数组中运行与for
循环相同的方式,即使较慢的method_while2
也是绑定数组的两倍。
即使在$_
中向method_while2
添加method_while3
和别名分配的本地化,也会比绑定数组的执行速度快66%。
for
中未发生的method_while3
循环中发生了哪些额外的工作?有没有办法提高绑定数组的速度?
答案 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中执行的代码越多,代码就会越快。