2009-02-13 9 views
14

Ich habe oft eine Subroutine in Perl, die ein Array mit einigen Informationen füllt. Da ich auch für Hacker in C++ gewohnt bin, finde ich mich tut es oft, wie dies in Perl, mit Referenzen:Wird ein ganzes Array von einer Perl-Subroutine ineffizient zurückgegeben?

my @array; 
getInfo(\@array); 

sub getInfo { 
    my ($arrayRef) = @_; 
    push @$arrayRef, "obama"; 
    # ... 
} 

anstelle der einfacheren Version:

my @array = getInfo(); 

sub getInfo { 
    my @array; 
    push @array, "obama"; 
    # ... 
    return @array; 
} 

Der Grund, von Natürlich möchte ich nicht, dass das Array lokal in der Subroutine erstellt und dann bei der Rückkehr kopiert wird.

Ist das richtig? Oder optimiert Perl das sowieso?

+0

Vielleicht könnten Sie auf einer höheren Ebene erklären, was Sie zu tun versuchen. Vielleicht gibt es eine modernere, Perlianische Art zu schreiben, was Sie wollen, um Ihr Problem zu vermeiden. Oder machen Sie zumindest Ihre Absichten klarer. –

+0

@unknown (google): Ich mache das oft in verschiedenen Kontexten. Das letzte Mal war, als ich eine Datei in ein Array lesen wollte. Also muss die Datei geöffnet werden, ich drucke Informationen darüber, welche Datei ich auf STDERR öffne, modifiziere die Zeilen ein wenig und schiebe sie auf das Array, dann schließe die Datei. – Frank

+0

ändern wie? Wie könnte es pro Zeile mit einer map-Anweisung gemacht werden? Ich finde etwas auf einem Array zu drücken, um sehr verdächtig zu sein, wieder zugunsten anderer Techniken. manchmal kann es nicht geholfen werden, obwohl –

Antwort

18

Was ist mit der Rückgabe einer Array-Referenz an erster Stelle?

sub getInfo { 
    my $array_ref = []; 
    push @$array_ref, 'foo'; 
    # ... 
    return $array_ref; 
} 

my $a_ref = getInfo(); 
# or if you want the array expanded 
my @array = @{getInfo()}; 

bearbeitet nach Dehmann Kommentar:

Es ist auch möglich, ein normales Array in der Funktion zu verwenden, und einen Verweis darauf zurück.

+0

Das klingt wie die beste Lösung für mich! – Powerlord

+0

Eigentlich, wie wäre es mit dem Erstellen eines realen Array in der Funktion, aber es einen Verweis darauf zurückgeben? Perl würde das lokal erzeugte Array am Leben erhalten und eine Referenz effizient zurückgeben. – Frank

+0

@dehmann: Guter Punkt, ich habe deinen Kommentar in meine Antwort aufgenommen, danke. – user55400

-4

Ich weiß nichts über Perl, also ist dies eine sprachneutrale Antwort.

Es ist in gewissem Sinn ineffizient, ein Array von einer Subroutine in das aufrufende Programm zu kopieren. Die Ineffizienz tritt in dem verwendeten zusätzlichen Speicher und der Zeit auf, die zum Kopieren der Daten von einem Ort zu einem anderen benötigt wird. Auf der anderen Seite, für alle, außer die größten Arrays, ist es Ihnen vielleicht egal, und vielleicht ziehen Sie es vor, Arrays aus Gründen der Eleganz, der Gefälligkeit oder aus irgendeinem anderen Grund herauszukopieren.

Die effiziente Lösung ist für das Unterprogramm, um dem aufrufenden Programm die Adresse des Arrays zu übergeben. Wie gesagt, ich habe keine Ahnung von Perls Standardverhalten in dieser Hinsicht. Einige Sprachen bieten dem Programmierer jedoch die Möglichkeit, sich für einen Ansatz zu entscheiden.

+0

Die "Adresse des Arrays" in Perl ist eine Referenz. Die Frage ist, ob Perl dafür optimiert. –

13

Das Übergeben von Referenzen ist effizienter, aber der Unterschied ist nicht so groß wie in C++. Die Argumentwerte selbst (dh die Werte im Array) werden immer als Referenz übergeben (die zurückgegebenen Werte werden jedoch kopiert).

Frage ist: spielt es eine Rolle? Meistens tut es das nicht. Wenn Sie 5 Elemente zurückgeben, kümmern Sie sich nicht darum. Wenn Sie 100'000 Elemente zurückgeben/weitergeben, verwenden Sie Referenzen. Optimiere es nur, wenn es ein Flaschenhals ist.

3

Um das letzte Wiederkäuen zu beantworten, nein, Perl optimiert das nicht. Es kann nicht wirklich, weil das Zurückgeben eines Arrays und das Zurückgeben eines Skalars grundlegend unterschiedlich sind.

Wenn Sie mit großen Datenmengen zu tun haben oder wenn Leistung eine große Rolle spielt, dann werden Ihnen Ihre C-Gewohnheiten gut tun - Verweise auf Datenstrukturen und nicht Strukturen selbst, die sie nicht benötigen kopiert werden. Aber, wie Leon Timmermans darauf hingewiesen hat, dass es sich bei der überwiegenden Mehrheit der Fälle um kleinere Datenmengen handelt und die Leistung nicht so groß ist, tun Sie es auf die am besten lesbare Weise.

8

Wenn ich an Ihrem Beispiel betrachten und darüber nachdenken, was Sie tun möchten, ich bin es gewohnt, es auf diese Weise zu schreiben:

sub getInfo { 
    my @array; 
    push @array, 'obama'; 
    # ... 
    return \@array; 
} 

Es scheint mir, als einfache Version, wenn ich zurückkommen müssen große Datenmenge.Es ist nicht erforderlich, Array außerhalb sub zuweisen, wie Sie in Ihrem ersten Code-Snippet geschrieben, weil my es für Sie tun. Auf jeden Fall sollten Sie keine vorzeitige Optimierung vornehmen, wie Leon Timmermanssuggest.

2

So würde ich normalerweise ein Array zurückgeben.

sub getInfo { 
    my @array; 
    push @array, 'foo'; 
    # ... 
    return @array if wantarray; 
    return \@array; 
} 

Auf diese Weise wird es, wie Sie wollen, in Skalar oder eine Liste Kontexten arbeiten.

my $array = getInfo; 
my @array = getInfo; 

$array->[0] == $array[0]; 

# same length 
@$array == @array; 

Ich würde nicht versuchen, es zu optimieren, es sei denn, Sie wissen, dass es ein langsamer Teil Ihres Codes ist. Selbst dann würde ich Benchmarks verwenden, um zu sehen, welche Subroutine tatsächlich schneller ist.

+0

Dann können Sie die Anzahl nicht abrufen, indem Sie getInfo() einem Skalarwert zuweisen. http://perlmonks.org/?node_id=729965 hat eine interessante Debatte über die Verwendung von Wantarray. – daotoad

+0

Ich stimme zu, ich hatte '' wantarray' 'vor etwa drei Jahren verwendet. Ich hatte eine schwierige Funktion. Nach vielen Jahren Erfahrung im großen Perl-Projekt mit vielen verschiedenen fähigen Entwicklern habe ich entschieden, dass kontextbewusster Code eines der schlimmsten Dinge in Perl ist. –

+1

@daotoad: Sie können niemals davon ausgehen, dass eine Funktion, die eine Liste im Listenkontext zurückgibt, ihre Länge im skalaren Kontext zurückgibt, da dies nur geschieht, wenn die Funktion ein Array zurückgibt. Wenn die Funktion einen Listenwert zurückgibt, erhalten Sie das letzte Element der Liste. Warum? Weil Perl dich hasst. :) –

2

Es gibt zwei Überlegungen. Die offensichtliche ist, wie groß wird Ihr Array werden? Wenn es weniger als ein paar Dutzend Elemente ist, dann ist die Größe kein Faktor (es sei denn, Sie sind Mikro-Optimierung für einige schnell aufgerufene Funktion, aber Sie müssten einige Speicher Profiling tun, um dies zuerst zu beweisen).

Das ist der einfache Teil. Die oft übersehene zweite Überlegung ist die Schnittstelle. Wie wird das zurückgegebene Array verwendet? Dies ist wichtig, weil die Dereferenzierung von ganzen Arrays in Perl ziemlich schlimm ist. Zum Beispiel:

for my $info (@{ getInfo($some, $args) }) { 
    ... 
} 

Das ist hässlich. Das ist viel besser.

Es eignet sich auch zum Mapping und Greppen.

my @info = grep { ... } getInfo($some, $args); 

Aber ein Array ref zurückkehren kann sehr nützlich sein, wenn Sie einzelne Elemente gehen zu herauszupicken:

my $address = (getInfo($some, $args))[2]; 

Oder:

my @info = getInfo($some, $args); 
my $address = $info[2]; 

my $address = getInfo($some, $args)->[2]; 

, die als einfacher ist

Aber an diesem Punkt, Sie sho Uld Frage, ob @info wirklich eine Liste oder ein Hash ist.

my $address = getInfo($some, $args)->{address}; 

Was Sie nicht tun sollten, ist haben getInfo() ein Array ref in Skalarkontext und ein Array im Listenkontext. Dies verwirrt die traditionelle Verwendung von skalaren Kontexten als Array-Länge, die den Benutzer überraschen wird.

Schließlich werde ich mein eigenes Modul, Method::Signatures, stecken, weil es einen Kompromiss für die Übergabe von Array-Referenzen bietet, ohne die Array-Ref-Syntax verwenden zu müssen.

use Method::Signatures; 

method foo(\@args) { 
    print "@args";  # @args is not a copy 
    push @args, 42; # this alters the caller array 
} 

my @nums = (1,2,3); 
Class->foo(\@nums); # prints 1 2 3 
print "@nums";  # prints 1 2 3 42 

Dies geschieht durch die Magie von Data::Alias.

+0

Sie können niemals davon ausgehen, dass eine Funktion, die eine Liste im Listenkontext zurückgibt, ihre Länge im skalaren Kontext zurückgibt, da dies nur geschieht, wenn die Funktion ein Array zurückgibt.Wenn die Funktion einen Nicht-Array-Listenwert zurückgibt, erhalten Sie anstelle der Größe das letzte Element der Liste. –

+0

Dann keine Listen zurück! Wenn dein Hammergriff dir Splitter gibt, trage keine Handschuhe, schleife sie glatt! Die ganze "List vs Array" Sache in Perl 5 ist eine riesige, klaffende Bärenfalle mitten auf dem Spielplatz. – Schwern

+0

Ich stimme deinem letzten Satz vollkommen zu. Ich würde hinzufügen, dass die meisten Kinder auf dem Spielplatz und vielleicht sogar die Spielplatzdesigner nicht von dieser Bärenfalle wissen. :) –

0

3 andere potenziell große Leistungsverbesserungen, wenn Sie eine ganze, ziemlich große Datei lesen und es in ein Array Slicing:

  1. mit sysread Schalten Sie BUFFERING() anstelle von read() (Handbuch warnt über Mischen)
  2. Pre-verlängern die Anordnung durch das letzte Element der Bewertung - spart Speicherzuordnungen
  3. Verwenden auspacken(), um rasch unterteilten Daten wie uint16_t Grafikkanaldaten

Durch die Übergabe eines Array-Ref an die Funktion kann das Hauptprogramm mit einem einfachen Array arbeiten, während die Write-Once-Forget-Worker-Funktion den komplizierteren "$ @" - und Pfeil-> [$ II] -Zugang verwendet Formen. Da es ziemlich C'ish ist, ist es wahrscheinlich schnell!