在Perl的foreach中,$ _比命名变量更有效吗?

时间:2009-01-28 08:58:56

标签: perl variables loops performance

我是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";
}

我想知道这一点,以便知道$ _的使用是否更有效,因为它是放在寄存器中,因为它是常用或不常用的。我已经编写了一些代码,我正在尝试清理它,并且我发现我使用的第一个循环比第二个循环更频繁。

8 个答案:

答案 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观察loop2my快约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)。