Является ли $ _ более эффективным, чем именованная переменная в foreach Perl? - PullRequest
8 голосов
/ 28 января 2009

Я новичок в 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 ]

14 голосов
/ 28 января 2009

Обнаружили ли вы, что есть проблемы с производительностью в разделах кода, которые используют эти циклы? Если нет, вы хотите выбрать тот, который более читабелен и, следовательно, более удобен в обслуживании. Любая разница в скорости, вероятно, будет незначительной, особенно по сравнению с другими частями вашей системы. Всегда сначала используйте код для сопровождения, затем профиль, затем код для производительности

«Преждевременная оптимизация - корень всего зла» [1]

[1] Кнут, Дональд. Структурированное программирование с переходом к отчетам, ACM Journal Computing Surveys, том 6, № 4, декабрь 1974 года.

11 голосов
/ 28 января 2009

Даже знаю Преждевременная оптимизация - корень всего зла

{
  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 примерно на 15-20% быстрее, чем loop2, но я не могу определить, почему.

Я наблюдал сгенерированный байт-код для loop1 и loop2, и при создании переменной my разница только одна. Эта переменная внутренняя область не выделяется и также не копируется, поэтому эта операция очень дешевая. Разница возникает, я думаю, только от конструкции "$_\n", которая недешева. Эти петли должны быть очень похожи

for (@numbers) {
  ...
}

for my $a (@numbers) {
  ...
}

но этот цикл дороже

for (@numbers) {
  my $a = $_;
  ...
}

а также

print "$a\n";

дороже

print $a, "\n";
6 голосов
/ 28 января 2009

Benchmark:

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%     --

Я запускал его пару раз с разными результатами. Я думаю, можно с уверенностью сказать, что нет большой разницы.

6 голосов
/ 28 января 2009

Вы могли бы взглянуть на в этом учебном пособии , также есть глава "Benchmark Your Code", которую можно использовать для сравнения этих двух способов.

2 голосов
/ 11 августа 2009

Выполнение двух опций через "perl -MO=Concise,-terse,-src test.pl", приводит к этим двум OpTrees:

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){...}".

2 голосов
/ 28 января 2009

меня больше интересует общая идея использования $ _ вместо печати ...

В качестве дополнительного примечания: Perl Best Practices - это хорошее место, куда можно обратиться, если вы хотите начать изучать, какие идиомы следует избегать и почему. Я не согласен со всем, что он пишет, но он в большинстве случаев.

1 голос
/ 28 января 2009

Использование $_ - это идиома Perl, которая показывает опытному программисту, что используется «текущий контекст». Кроме того, многие функции по умолчанию принимают $_ в качестве параметра, что делает код более лаконичным.

Некоторые могут также утверждать, что «это было трудно писать, это должно быть трудно читать».

0 голосов
/ 28 января 2009

Не знаю, но ... ну, во-первых, вы сохраняете присвоение переменной во второй версии цикла. Я могу себе представить, что поскольку $ _ используется очень часто, его нужно как-то оптимизировать. Вы могли бы попытаться профилировать его, очень хороший Perl-профилировщик - NYTProf 2 , написанный Тимом Бансом.

Тогда действительно ли стоит оптимизировать эти мелочи? Я не думаю, что петля будет иметь значение. Я предлагаю вам использовать профилировщик для измерения вашей производительности и выявления реальных узких мест. Обычно проблемы со скоростью находятся в 10% кода, который выполняется 90% времени (возможно, не будет 10-90, но это «знаменитое» соотношение: P).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...