Найкращий спосіб ітерації через масив Perl


94

Яка найкраща реалізація (з точки зору швидкості та використання пам'яті) для ітерації через масив 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
Два з трьох, які ви опублікували, змусили б мене піти "WTH ?!" якщо тільки немає додаткового оточуючого контексту, щоб зробити їх розумними альтернативами. У будь-якому випадку це питання знаходиться на рівні « Який найкращий спосіб додати два числа? » Більшість випадків існує лише один спосіб. Тоді, є ті обставини, коли вам потрібен інший шлях. Голосування про закриття.
Сінан Юнюр

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

2
У розділі 24 третього випуску програмування 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
Торстен Schöning

@ikegami Вірно вашому стилю чемпіона - чудова відповідь :)
skeetastax

26

Якщо ви дбаєте лише про елементи @Array, використовуйте:

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

або

Якщо індекси мають значення, використовуйте:

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

Або станом на perl5.12.1 ви можете використовувати:

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

Якщо вам потрібен і елемент, і його індекс в тілі циклу, Я б очікував використовуючи each бути найшвидшим, але тодіви відмовитесь від сумісності з попередніми 5.12.1 perlс.

За певних обставин може бути доречним інший зразок, ніж цей.


Я б очікував, що eachце буде найбільш повільним. Він виконує всю роботу інших мінус псевдонім, плюс призначення списку, дві скалярні копії та дві скалярні очистки.
ikegami

І в міру моїх можливостей вимірювання ви маєте рацію. Близько на 45% швидше з forітерацією над індексами масиву і на 20% швидше при ітерації над індексами масиву посилання (я роблю доступ $array->[$i]в тілі), над використанням eachразом із while.
Сінан Юнюр

3

IMO, реалізація №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 колись посилається = = 0, пам'ять, що займається, стає вторинною.
CodeClown42

@ikegami: Я бачу річ у відношенні ()vs undef, але якщо вихід із сфери застосування не вивільняє пам'ять, яку використовує масив, локальний для цього масштабу, чи це не зробить perl протікаючою катастрофою? Це не може бути правдою.
CodeClown42

Вони також не протікають. Підрозділ як і раніше володіє ними, і повторно їх використовуватиме наступного разу, коли він буде викликаний. Оптимізовано для швидкості.
ікегами

1

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

друкувати $ _ для (@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, підрив 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це не довжина масиву, а останній його індекс.
Джош
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.