我正在对Perl性能进行一些基准测试,遇到了一个我认为有些奇怪的案例。假设您有一个函数多次使用数组中的值。在这种情况下,您通常会看到一些代码:
sub foo {
my $value = $array[17];
do_something_with($value);
do_something_else_with($value);
}
另一种选择是根本不创建局部变量:
sub foo {
do_something_with($array[17]);
do_something_else_with($array[17]);
}
为便于阅读,第一个更清晰。我还认为在第一种情况下性能至少也要相等(或更高)-毕竟数组查找需要乘加运算。
想象一下,当这个测试程序显示相反的结果时,我感到惊讶。在我的机器上,重新执行数组查找实际上比存储结果快得多,直到将ITERATIONS增加到7;换句话说,对我来说,创建局部变量只有在至少使用7次的情况下才是值得的!
use Benchmark qw(:all);
use constant { ITERATIONS => 4, TIME => -5 };
# sample array
my @array = (1 .. 100);
cmpthese(TIME, {
# local variable version
'local_variable' => sub {
my $index = int(rand(scalar @array));
my $val = $array[$index];
my $ret = '';
for (my $i = 0; $i < ITERATIONS; $i ++) {
$ret .= $val;
}
return $ret;
},
# multiple array access version
'multi_access' => sub {
my $index = int(rand(scalar @array));
my $ret = '';
for (my $i = 0; $i < ITERATIONS; $i ++) {
$ret .= $array[$index];
}
return $ret;
}
});
结果:
Rate local_variable multi_access
local_variable 245647/s -- -5%
multi_access 257907/s 5% --
这不是一个巨大的区别,但是它提出了我的问题:为什么创建一个局部变量并缓存数组查找要比再次查找慢呢?帖子中,我已经看到其他语言/编译器确实具有预期的结果,有时甚至可以将它们转换为相同的代码。 Perl在做什么?
答案 0 :(得分:1)
让我们首先声明:缓存缓存将避免重复查找,尽管这样做虽然会花费一些时间,但预计会更快,并且一旦完成7次以上查找,它就会开始变得更快。我认为,现在还不那么令人震惊。
关于为什么它的迭代速度少于七个迭代速度的原因……我想,标量创建的成本仍然大于这几个查询。它肯定大于一次查找,是吗?那两个呢?我会说“几个”可能是一个好方法。
答案 1 :(得分:1)
我今天对此做了更多的探索,我确定的是,相对于间接费用,标量赋值 是一项昂贵的操作一深度数组查找。
这似乎只是在重申最初的问题,但我觉得我已经更加清楚了。例如,如果我修改了我的local_variable子例程来进行另一种分配,例如:
my $index = int(rand(scalar @array));
my $val = 0; # <- this is new
$val = $array[$index];
my $ret = '';
...除了单赋值版本外,该代码还会遭受5%的速度损失-即使它仅对变量进行虚拟赋值。
我还测试了示波器是否将$var
的设置/拆卸造成阻碍性能,方法是将其切换到全局而不是本地范围。差异可以忽略不计(请参阅上面对@zdim的评论),将远离指向构造/破坏作为性能瓶颈。
最后,我的困惑是基于错误的假设,即标量分配应该很快。我曾经在C语言中工作过,在C语言中,将值复制到本地变量是非常快速的操作(1-2条ASM指令)。
事实证明,在Perl中并非如此(尽管我不确切地知道 为什么,这没关系)。标量分配是一个相对“缓慢”的操作...相较之下,Perl内部人员为获得Array对象的第n个元素所做的一切实际上都非常快。我在第一篇文章中提到的“乘加”仍然比标量分配的代码少得多。
这就是为什么要进行大量查找才能匹配结果缓存性能的原因:简单地分配给“ cache”变量要慢7倍(对于我的设置)。