在我的测试中,我注意到迭代绑定数组最多只能使用内部访问器方法(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_while和method_while2似乎以与for循环相同的方式在绑定数组上运行,但即使较慢的method_while2也是两倍快速作为绑定数组.
甚至在method_while3中向method_while2添加$_和别名赋值的本地化导致执行比绑定数组快66%.
在for循环中发生了什么额外的工作,在method_while3中没有发生?有没有办法提高绑定阵列的速度?
解决方法
在基准测试中,你做到了
... 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% --