Лучший способ перебрать массив Perl

Какова лучшая реализация (с точки зрения скорости и использования памяти) для перебора массива Perl? Есть ли лучший способ? (@Array не должны быть сохранены).

Реализация 1

foreach (@Array)
{
      SubRoutine($_);
}

Реализация 2

while($Element=shift(@Array))
{
      SubRoutine($Element);
}

Реализация 3

while(scalar(@Array) !=0)
{
      $Element=shift(@Array);
      SubRoutine($Element);
}

Реализация 4

for my $i (0 .. $#Array)
{
      SubRoutine($Array[$i]);
}

Реализация 5

map { SubRoutine($_) } @Array ;

6 ответов

Решение
  • С точки зрения скорости: № 1 и № 4, но не намного в большинстве случаев.

    Вы можете написать тест для подтверждения, но я подозреваю, что вы найдете # 1 и # 4 немного быстрее, потому что итерационная работа выполняется в C вместо Perl, и не происходит ненужного копирования элементов массива. ($_ связан с элементом в #1, но #2 и #3 фактически копируют скаляры из массива.)

    # 5 может быть похожим.

  • С точки зрения использования памяти: они все одинаковые, кроме #5.

    for (@a) имеет специальный корпус, чтобы избежать сплющивания массива. Цикл перебирает индексы массива.

  • С точки зрения читабельности: #1.

  • С точки зрения гибкости: № 1/ № 4 и № 5.

    #2 не поддерживает ложные элементы. № 2 и № 3 разрушительны.

Если вы заботитесь только об элементах @Array, используйте:

for my $el (@Array) {
# ...
}

или же

Если показатели имеют значение, используйте:

for my $i (0 .. $#Array) {
# ...
}

Или, по состоянию на perl 5.12.1, вы можете использовать:

while (my ($i, $el) = each @Array) {
# ...
}

Если вам нужен элемент и его индекс в теле цикла, Я бы ожидал с помощью eachбыть самым быстрым, но потом вы отказываетесь от совместимости с pre-5.12.1 perls.

Какой-то другой шаблон, кроме этого, может быть подходящим при определенных обстоятельствах.

IMO, реализация #1 типична и коротка и идиоматична для Perl, и она опережает остальных. Тест из трех вариантов может предложить вам, по крайней мере, понимание скорости.

Лучший способ решить подобные вопросы, чтобы сравнить их:

use strict;
use warnings;
use Benchmark qw(:all);

our @input_array = (0..1000);

my $a = sub {
    my @array = @{[ @input_array ]};
    my $index = 0;
    foreach my $element (@array) {
       die unless $index == $element;
       $index++;
    }
};

my $b = sub {
    my @array = @{[ @input_array ]};
    my $index = 0;
    while (defined(my $element = shift @array)) {
       die unless $index == $element;
       $index++;
    }
};

my $c = sub {
    my @array = @{[ @input_array ]};
    my $index = 0;
    while (scalar(@array) !=0) {
       my $element = shift(@array);
       die unless $index == $element;
       $index++;
    }
};

my $d = sub {
    my @array = @{[ @input_array ]};
    foreach my $index (0.. $#array) {
       my $element = $array[$index];
       die unless $index == $element;
    }
};

my $e = sub {
    my @array = @{[ @input_array ]};
    for (my $index = 0; $index < $#array; $index++) {
       my $element = $array[$index];
       die unless $index == $element;
    }
};

my $f = sub {
    my @array = @{[ @input_array ]};
    while (my ($index, $element) = each @array) {
       die unless $index == $element;
    }
};

my $count;
timethese($count, {
   '1' => $a,
   '2' => $b,
   '3' => $c,
   '4' => $d,
   '5' => $e,
   '6' => $f,
});

И запуск этого на Perl 5, версия 24, Subversion 1 (v5.24.1), созданная для x86_64-linux-gnu-thread-multi

Я получил:

Benchmark: running 1, 2, 3, 4, 5, 6 for at least 3 CPU seconds...
         1:  3 wallclock secs ( 3.16 usr +  0.00 sys =  3.16 CPU) @ 12560.13/s (n=39690)
         2:  3 wallclock secs ( 3.18 usr +  0.00 sys =  3.18 CPU) @ 7828.30/s (n=24894)
         3:  3 wallclock secs ( 3.23 usr +  0.00 sys =  3.23 CPU) @ 6763.47/s (n=21846)
         4:  4 wallclock secs ( 3.15 usr +  0.00 sys =  3.15 CPU) @ 9596.83/s (n=30230)
         5:  4 wallclock secs ( 3.20 usr +  0.00 sys =  3.20 CPU) @ 6826.88/s (n=21846)
         6:  3 wallclock secs ( 3.12 usr +  0.00 sys =  3.12 CPU) @ 5653.53/s (n=17639)

Таким образом, foreach (@Array) примерно в два раза быстрее других. Все остальные очень похожи.

@ikegami также указывает на то, что существует не так много различий в этих значениях, кроме скорости.

В одну строку вывести элемент или массив.

выведите $_ for (@array);

ПРИМЕЧАНИЕ: помните, что $ _ внутренне ссылается на элемент @array в цикле. Любые изменения, сделанные в $ _, будут отражены в @array; ех.

my @array = qw( 1 2 3 );
for (@array) {
        $_ = $_ *2 ;
}
print "@array";

выход: 2 4 6

1 существенно отличается от 2 и 3, поскольку он оставляет массив в такте, тогда как два других оставляют его пустым.

Я бы сказал, что #3 довольно дурацкий и, вероятно, менее эффективный, так что забудьте об этом.

Что оставляет вас с № 1 и № 2, и они не делают одно и то же, поэтому один не может быть "лучше", чем другой. Если массив большой, и вам не нужно его хранить, обычно с ним справится область действия (но см. ПРИМЕЧАНИЕ), поэтому, как правило, #1 по-прежнему является наиболее простым и понятным методом. Отключение каждого элемента ничего не ускорит. Даже если есть необходимость освободить массив от ссылки, я бы просто пошел:

undef @Array;

когда сделано.

  • ПРИМЕЧАНИЕ. Подпрограмма, содержащая область действия массива, фактически сохраняет массив и повторно использует пространство в следующий раз. Как правило, это должно быть хорошо (см. Комментарии).
Другие вопросы по тегам