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


96

Какая реализация (с точки зрения скорости и использования памяти) для перебора массива 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 ;

2
Почему должен быть «лучший»? Особенно с учетом того, что мы понятия не имеем, как бы вы сравнили одно с другим (скорость важнее, чем использование памяти? Это mapприемлемый ответ? И т. Д.)
Макс Либберт

2
Двое из трех, которые вы опубликовали, заставили бы меня сказать "ЧТО ?!" если только нет дополнительного окружающего контекста, чтобы сделать их разумными альтернативами. В любом случае, этот вопрос находится на уровне « Как лучше всего сложить два числа? » В большинстве случаев есть только один способ. Тогда есть те обстоятельства, когда вам нужен другой путь. Голосование закрыто.
Sinan Ünür

4
@ SinanÜnür Я сочувствую вашему мнению (что есть только один способ сложить два числа), но аналогия недостаточно сильна, чтобы использовать ее пренебрежительно. Очевидно, что существует несколько способов, и ОП хочет понять, какая идея хорошая, а какая нет.
CodeClown42

2
В главе 24 третьего издания Programming Perl есть раздел об эффективности, который стоит прочитать. Он обращается к различным типам эффективности, таким как время, программист, сопровождающий. Раздел начинается с утверждения: «Обратите внимание, что оптимизация по времени может иногда стоить вам места или эффективности программиста (на что указывают противоречивые подсказки ниже).

1
Один 1 способ сложить два числа? Нет, если вы посмотрите на вызовы / реализации более низкого уровня .... подумайте о переносе просмотра
вперед

Ответы:


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

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

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

  • Что касается использования памяти: все они одинаковы, за исключением №5.

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

  • По удобочитаемости: №1.

  • По гибкости: №1 / №4 и №5.

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


3
Вау, вы добавили массу информации короткими и простыми предложениями.
jaypal Singh

1
№2 хорош, когда вы делаете очереди (например, поиск в ширину):my @todo = $root; while (@todo) { my $node = shift; ...; push @todo, ...; ...; }
ikegami

Разве реализация 4 не создает промежуточный массив индексов, который может потребовать большого объема используемой памяти? Если так, похоже, что не следует использовать такой подход. stackoverflow.com/questions/6440723/… rt.cpan.org/Public/Bug/Display.html?id=115863
Торстен Шёнинг

@ikegami Верный твоему чемпионскому стилю - отличный ответ :)
sceetastax

26

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

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

или

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

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

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

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

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

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


Я ожидал, что он eachбудет самым медленным. Он выполняет всю работу остальных за вычетом псевдонима, плюс назначение списка, две скалярные копии и две скалярные очистки.
ikegami

И, насколько я могу измерить, вы правы. Примерно на 45% быстрее при forитерации по индексам массива и на 20% быстрее при итерации по индексам ссылки на массив (я выполняю доступ $array->[$i]в теле), по сравнению с использованием eachв сочетании с while.
Sinan Ünür

3

ИМО, реализация №1 типична, короткая и идиоматическая для Perl превосходит другие только в этом. По крайней мере, эталонный тест из трех вариантов может дать вам представление о скорости.


2

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

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

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

undef @Array;

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

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

@Array = ();не освобождает базовый массив. Даже выход за рамки этого не сделает. Если вы хотите освободить базовый массив, вы должны использовать undef @Array;.
ikegami

2
Демо; perl -MDevel::Peek -e'my @a; Dump(\@a,1); @a=qw( a b c ); Dump(\@a,1); @a=(); Dump(\@a,1); undef @a; Dump(\@a,1);' 2>&1 | grep ARRAY
ikegami

КАКИЕ??? Я думал, что вся суть GC заключается в том, что когда-то ref count == 0, задействованная память становится пригодной для повторного использования.
CodeClown42

@ikegami: Я понимаю, что такое ()vs undef, но если выход за пределы области действия не освобождает память, используемую локальным для этой области массивом, разве это не превращает Perl в утечку? Это не может быть правдой.
CodeClown42

Они тоже не протекают. Субподрядчик по-прежнему владеет ими и будет использовать их повторно при следующем вызове подпрограммы. Оптимизирован для скорости.
ikegami

1

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

напечатать $ _ for (@array);

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

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

вывод: 2 4 6


0

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

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 также отмечает, что помимо скорости есть немало различий в этих имплементациях.


1
На $index < $#arrayсамом деле сравнение должно происходить, $index <= $#arrayпотому что $#arrayэто не длина массива, а его последний индекс.
josch 05
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.