我是Perl的新手,我想知道以下哪个循环效率更高:
my @numbers = (1,3,5,7,9);
foreach my $current (@numbers){
print "$current\n";
}
或
my @numbers = (1,3,5,7,9);
foreach (@numbers){
print "$_\n";
}
我想知道这一点,以便知道$ _的使用是否更有效,因为它是放在寄存器中,因为它是常用或不常用的。我已经编写了一些代码,我正在尝试清理它,并且我发现我使用的第一个循环比第二个循环更频繁。
答案 0 :(得分:14)
您是否发现在使用这些循环的代码段中存在性能问题?如果没有,您希望选择更具可读性且更易于维护的那个。速度的任何差异都可能是微不足道的,特别是与系统的其他部分相比。始终首先编码可维护性,然后编写配置文件,然后编写性能代码
“过早优化是所有邪恶的根源”[1]
[1] Knuth,唐纳德。结构化编程,参见陈述,ACM期刊计算调查,第6卷,第4期,1974年12月。第268页。
答案 1 :(得分:11)
即使知道过早优化是所有邪恶的根源
{
local $\ = "\n";
print foreach @numbers;
}
但有些期望可能是错误的。测试有点奇怪,因为输出会产生一些奇怪的副作用,顺序可能很重要。
#!/usr/bin/env perl
use strict;
use warnings;
use Benchmark qw(:all :hireswallclock);
use constant Numbers => 10000;
my @numbers = (1 .. Numbers);
sub no_out (&) {
local *STDOUT;
open STDOUT, '>', '/dev/null';
my $result = shift()->();
close STDOUT;
return $result;
};
my %tests = (
loop1 => sub {
foreach my $current (@numbers) {
print "$current\n";
}
},
loop2 => sub {
foreach (@numbers) {
print "$_\n";
}
},
loop3 => sub {
local $\ = "\n";
print foreach @numbers;
}
);
sub permutations {
return [
map {
my $a = $_;
my @f = grep {$a ne $_} @_;
map { [$a, @$_] } @{ permutations( @f ) }
} @_
]
if @_;
return [[]];
}
foreach my $p ( @{ permutations( keys %tests ) } ) {
my $result = {
map {
$_ => no_out { sleep 1; countit( 2, $tests{$_} ) }
} @$p
};
cmpthese($result);
}
可以预期loop2应该比loop1
更快 Rate loop2 loop1 loop3
loop2 322/s -- -2% -34%
loop1 328/s 2% -- -33%
loop3 486/s 51% 48% --
Rate loop2 loop1 loop3
loop2 322/s -- -0% -34%
loop1 323/s 0% -- -34%
loop3 486/s 51% 50% --
Rate loop2 loop1 loop3
loop2 323/s -- -0% -33%
loop1 324/s 0% -- -33%
loop3 484/s 50% 49% --
Rate loop2 loop1 loop3
loop2 317/s -- -3% -35%
loop1 328/s 3% -- -33%
loop3 488/s 54% 49% --
Rate loop2 loop1 loop3
loop2 323/s -- -2% -34%
loop1 329/s 2% -- -33%
loop3 489/s 51% 49% --
Rate loop2 loop1 loop3
loop2 325/s -- -1% -33%
loop1 329/s 1% -- -32%
loop3 488/s 50% 48% --
有时候我始终loop1
观察loop2
比my
快约15%-20%,但我无法确定原因。
我观察到为loop1和loop2生成了字节码,并且在创建"$_\n"
变量时只有一个差异。该变量内部未被分配也未被复制,因此该操作非常便宜。差异来自我认为仅来自for (@numbers) {
...
}
for my $a (@numbers) {
...
}
构造并不便宜。这些循环应该非常相似
for (@numbers) {
my $a = $_;
...
}
但这个循环更贵
print "$a\n";
以及
print $a, "\n";
比
贵{{1}}
答案 2 :(得分:6)
您可以查看this tutorial,还可以使用“基准代码”这一章来比较这两种方式。
答案 3 :(得分:6)
基准:
use Benchmark qw(timethese cmpthese);
my $iterations = 500000;
cmpthese( $iterations,
{
'Loop 1' => 'my @numbers = (1,3,5,7,9);
foreach my $current (@numbers)
{
print "$current\n";
}',
'Loop 2' => 'my @numbers = (1,3,5,7,9);
foreach (@numbers)
{
print "$_\n";
}'
}
);
输出:
Rate Loop 2 Loop 1
Loop 2 23375/s -- -1%
Loop 1 23546/s 1% --
我已经运行了几次,结果各不相同。我认为可以说没有太大区别。
答案 4 :(得分:2)
我对一般的想法更感兴趣 使用$ _而不是打印...
作为旁注, Perl最佳实践是一个很好的去处,如果你想开始学习避免哪些成语以及为什么。我不同意他写的所有内容,但大部分时间都是他的观点。
答案 5 :(得分:2)
通过“perl -MO=Concise,-terse,-src test.pl
”运行两个选项会产生这两个OpTree:
<强> for my $n (@num){ ... }
强>
LISTOP (0x9c08ea0) leave [1] OP (0x9bad5e8) enter # 5: my @num = 1..9; COP (0x9b89668) nextstate BINOP (0x9b86210) aassign [4] UNOP (0x9bacfa0) null [142] OP (0x9b905e0) pushmark UNOP (0x9bad5c8) rv2av SVOP (0x9bacf80) const [5] AV (0x9bd81b0) UNOP (0x9b895c0) null [142] OP (0x9bd95f8) pushmark OP (0x9b4b020) padav [1] # 6: for my $n (@num){ COP (0x9bd12a0) nextstate BINOP (0x9c08b48) leaveloop LOOP (0x9b1e820) enteriter [6] OP (0x9b1e808) null [3] UNOP (0x9bd1188) null [142] OP (0x9bb5ab0) pushmark OP (0x9b8c278) padav [1] UNOP (0x9bdc290) null LOGOP (0x9bdc2b0) and OP (0x9b1e458) iter LISTOP (0x9b859b8) lineseq # 7: say $n; COP (0x9be4f18) nextstate LISTOP (0x9b277c0) say OP (0x9c0edd0) pushmark OP (0x9bda658) padsv [6] # <=== OP (0x9b8a2f8) unstack
<强> for(@num){ ... }
强>
LISTOP (0x8cdbea0) leave [1] OP (0x8c805e8) enter # 5: my @num = 1..9; COP (0x8c5c668) nextstate BINOP (0x8c59210) aassign [4] UNOP (0x8c7ffa0) null [142] OP (0x8ccc1f0) pushmark UNOP (0x8c805c8) rv2av SVOP (0x8c7ff80) const [7] AV (0x8cab1b0) UNOP (0x8c5c5c0) null [142] OP (0x8cac5f8) pushmark OP (0x8c5f278) padav [1] # 6: for (@num){ COP (0x8cb7f18) nextstate BINOP (0x8ce1de8) leaveloop LOOP (0x8bf1820) enteriter OP (0x8bf1458) null [3] UNOP (0x8caf2b0) null [142] OP (0x8bf1808) pushmark OP (0x8c88ab0) padav [1] PADOP (0x8ca4188) gv GV (0x8bd7810) *_ # <=== UNOP (0x8cdbb48) null LOGOP (0x8caf290) and OP (0x8ce1dd0) iter LISTOP (0x8c62aa8) lineseq # 7: say $_; COP (0x8cade88) nextstate LISTOP (0x8bf12d0) say OP (0x8cad658) pushmark UNOP (0x8c589b8) null [15] # <=== PADOP (0x8bfa7c0) gvsv GV (0x8bd7810) *_ # <=== OP (0x8bf9a10) unstack
我添加了“<===
”来标记两者之间的差异。
如果您注意到“for(@num){...}
”版本实际上有更多操作。
因此,如果“for(@num){...}
”版本可能比“for my $n (@num){...}
”版本慢。
答案 6 :(得分:1)
使用$_
是一个Perl习语,它向经验丰富的程序员展示了使用“当前上下文”。此外,许多函数默认使用$_
作为参数,从而使代码更简洁。
有些人可能也会争辩说,“这很难写,应该很难读”。
答案 7 :(得分:0)
我不知道但是......首先,你要在循环的第二个版本中保存变量赋值。我可以想象,因为$ _经常使用它应该以某种方式进行优化。您可以尝试对其进行分析,Tim Bunce编写的NYTProf 2非常好的Perl分析器。
那么,优化这些小东西真的值得吗?我不认为循环会有所作为。我建议您使用分析器来衡量您的性能并确定真正的瓶颈。通常速度问题位于运行90%的代码的10%代码中(可能不会是10-90,但这是“着名的”比率:P)。