Der beste Weg, um ein Perl-Array zu durchlaufen


93

Welches ist die beste Implementierung (in Bezug auf Geschwindigkeit und Speichernutzung) für die Iteration durch ein Perl-Array? Gibt es einen besseren Weg? ( @Arraymuss nicht beibehalten werden).

Implementierung 1

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

Implementierung 2

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

Implementierung 3

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

Implementierung 4

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

Implementierung 5

map { SubRoutine($_) } @Array ;

2
Warum sollte es ein "Bestes" geben? Vor allem, weil wir keine Ahnung haben, wie Sie sich messen würden (ist Geschwindigkeit wichtiger als Speichernutzung? map
Ist

2
Zwei der drei, die du gepostet hast, würden mich dazu bringen, "WTH?!" es sei denn, es gibt einen zusätzlichen umgebenden Kontext, um sie zu sinnvollen Alternativen zu machen. In jedem Fall befindet sich diese Frage auf der Ebene " Was ist der beste Weg, um zwei Zahlen hinzuzufügen? ". Meistens gibt es nur einen Weg. Dann gibt es solche Umstände, unter denen Sie einen anderen Weg brauchen. Abstimmung zum Schließen.
Sinan Ünür

4
@ SinanÜnür Ich kann mich in Ihre Meinung einfühlen (dass es nur einen Weg gibt, zwei Zahlen zu addieren), aber die Analogie ist nicht stark genug, um sie abweisend zu verwenden. Offensichtlich gibt es mehr als einen Weg, und das OP möchte verstehen, was eine gute Idee ist und was nicht.
CodeClown42

2
Kapitel 24 der dritten Ausgabe von Programming Perl enthält einen Abschnitt über Effizienz, der gut gelesen werden kann. Es befasst sich mit den verschiedenen Arten von Effizienz wie Zeit, Programmierer, Betreuer. Der Abschnitt beginnt mit der Aussage "Beachten Sie, dass die Optimierung der Zeit manchmal Platz oder Effizienz des Programmierers kostet (angezeigt durch widersprüchliche Hinweise unten). Dies sind die Pausen."

1
Eine Möglichkeit, zwei Zahlen hinzuzufügen? Nicht, wenn Sie sich mit Aufrufen / Implementierungen auf niedrigerer Ebene
befassen

Antworten:


75
  • In Bezug auf die Geschwindigkeit: # 1 und # 4, aber in den meisten Fällen nicht viel.

    Sie könnten einen Benchmark schreiben, um dies zu bestätigen, aber ich vermute, Sie werden feststellen, dass Nr. 1 und Nr. 4 etwas schneller sind, da die Iterationsarbeit in C anstelle von Perl ausgeführt wird und kein unnötiges Kopieren der Array-Elemente erfolgt. ( $_Wird aliased in # 1, zu dem Elemente , aber # 2 und # 3 tatsächlich kopiert die Skalare aus dem Array.)

    # 5 könnte ähnlich sein.

  • In Bezug auf die Speichernutzung: Sie sind bis auf # 5 alle gleich.

    for (@a)ist ein spezielles Gehäuse, um ein Abflachen des Arrays zu vermeiden. Die Schleife durchläuft die Indizes des Arrays.

  • In Bezug auf die Lesbarkeit: # 1.

  • In Bezug auf Flexibilität: # 1 / # 4 und # 5.

    # 2 unterstützt keine Elemente, die falsch sind. # 2 und # 3 sind destruktiv.


3
Wow, Sie haben in kurzen und einfachen Sätzen jede Menge Informationen hinzugefügt.
Jaypal Singh

1
# 2 ist gut, wenn Sie Warteschlangen durchführen (z. B. my @todo = $root; while (@todo) { my $node = shift; ...; push @todo, ...; ...; }
Breitensuche

Erstellt Implementierung 4 nicht ein Zwischenarray von Indizes, das möglicherweise eine große Menge an zu verwendendem Speicher einführt? Wenn ja, klingt es so, als sollte man diesen Ansatz nicht verwenden. stackoverflow.com/questions/6440723/… rt.cpan.org/Public/Bug/Display.html?id=115863
Thorsten Schöning

26

Wenn Sie sich nur für die Elemente von interessieren @Array, verwenden Sie:

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

oder

Wenn die Indizes wichtig sind, verwenden Sie:

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

Oder, wie es von perl5.12.1, können Sie:

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

Wenn Sie sowohl das Element als auch seinen Index im Hauptteil der Schleife benötigen, Ich würde erwarten mit each am schnellsten sein, aber dannSie werden die Kompatibilität mit Pre-5.12.1 aufgeben perl.

Ein anderes Muster als dieses kann unter bestimmten Umständen angemessen sein.


Ich würde erwarten each, dass das am langsamsten ist. Es erledigt die ganze Arbeit der anderen abzüglich eines Alias ​​plus einer Listenzuweisung, zwei skalaren Kopien und zwei skalaren Löschungen.
Ikegami

Und nach bestem Wissen und Gewissen haben Sie Recht. Etwa 45% schneller beim forIterieren über Indizes eines Arrays und 20% schneller beim Iterieren über die Indizes einer Array-Referenz (ich greife $array->[$i]im Body zu) über die Verwendung eachin Verbindung mit while.
Sinan Ünür

3

IMO, Implementierung Nr. 1 ist typisch und kurz und idiomatisch für Perl zu sein, übertrumpft die anderen allein dafür. Ein Benchmark der drei Auswahlmöglichkeiten bietet Ihnen möglicherweise zumindest einen Einblick in die Geschwindigkeit.


2

1 unterscheidet sich wesentlich von 2 und 3, da das Array intakt bleibt, während die beiden anderen es leer lassen.

Ich würde sagen, # 3 ist ziemlich verrückt und wahrscheinlich weniger effizient, also vergiss das.

Was Sie mit # 1 und # 2 belässt und sie nicht das Gleiche tun, so dass einer nicht "besser" sein kann als der andere. Wenn das Array groß ist und Sie es nicht behalten müssen, wird es im Allgemeinen vom Gültigkeitsbereich behandelt ( siehe HINWEIS ). Daher ist # 1 im Allgemeinen immer noch die klarste und einfachste Methode. Das Ausschalten jedes Elements beschleunigt nichts. Selbst wenn das Array von der Referenz befreit werden muss, würde ich einfach gehen:

undef @Array;

wenn fertig.

  • ANMERKUNG : Die Unterroutine, die den Bereich des Arrays enthält, behält das Array tatsächlich bei und verwendet den Speicherplatz beim nächsten Mal wieder. Im Allgemeinen sollte das in Ordnung sein (siehe Kommentare).

@Array = ();gibt das zugrunde liegende Array nicht frei. Das würde nicht einmal aus dem Rahmen gehen. Wenn Sie das zugrunde liegende Array freigeben möchten, müssen Sie es verwenden undef @Array;.
Ikegami

2
Demo; 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

WAS??? Ich hatte gedacht, der springende Punkt bei GC sei einmal eine Ref-Zählung == 0, der betreffende Speicher wird recycelbar.
CodeClown42

@ikegami: Ich sehe die Sache mit ()vs undef, aber wenn das Verlassen des Bereichs nicht den Speicher freigibt , der von einem lokalen Array in diesem Bereich verwendet wird, macht das Perl dann nicht zu einer undichten Katastrophe? Das kann nicht wahr sein.
CodeClown42

Sie lecken auch nicht. Das Sub besitzt sie weiterhin und wird sie beim nächsten Aufruf des Sub wiederverwenden. Auf Geschwindigkeit optimiert.
Ikegami

1

In einer Zeile, um das Element oder Array zu drucken.

print $ _ for (@array);

HINWEIS: Denken Sie daran, dass $ _ intern auf das Element von @array in loop verweist. Alle in $ _ vorgenommenen Änderungen werden in @array angezeigt. Ex.

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

Ausgabe: 2 4 6


0

Der beste Weg, um Fragen wie diese zu entscheiden, um sie zu bewerten:

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,
});

Und dies auf Perl 5, Version 24, Subversion 1 (v5.24.1), ausgeführt für x86_64-linux-gnu-thread-multi

Ich bekomme:

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)

Das 'foreach (@Array)' ist also ungefähr doppelt so schnell wie die anderen. Alle anderen sind sehr ähnlich.

@ikegami weist auch darauf hin, dass es bei diesen Implikationen außer der Geschwindigkeit einige Unterschiede gibt.


1
Der Vergleich $index < $#arraysollte eigentlich sein, $index <= $#arrayweil $#arraynicht die Länge des Arrays, sondern der letzte Index davon ist.
Josch
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.