8 Stimmen

Haben Sie einen Leistungseinbruch beim Kopieren von Daten, wenn Sie Argumente an Perl-Subroutinen übergeben?

Ich habe an mehreren Perl-Skripten gearbeitet, die große Datendateien mit fester Breite verarbeiten und kleine Teilstrings aus jedem Datensatz extrahieren. Ich hatte mir vorgestellt, dass das Delegieren des Extrahierens von Teilstrings an Methodenaufrufe wegen des Overheads beim Kopieren des Datensatzes in das @_-Array kostspielig sein würde. Also habe ich die folgenden Tests durchgeführt, um (a) den direkten Aufruf von substr(), (b) den Methodenaufruf, der den Datensatz als String übergibt, und (c) den Methodenaufruf, der den Datensatz per Referenz übergibt, zu vergleichen.

use strict;
use warnings;
use Benchmark qw(timethese);

my $RECORD = '0' x 50000;

my $direct = sub { my $v = substr( $RECORD, $_, 1) for 0..999 };
my $byVal  = sub { my $v = ByVal ( $RECORD, $_)    for 0..999 };
my $byRef  = sub { my $v = ByRef (\$RECORD, $_)    for 0..999 };

sub ByVal { return substr(   $_[0], $_[1], 1) }
sub ByRef { return substr(${$_[0]}, $_[1], 1) }

timethese( 10000, {
    direct    => $direct,
    byVal     => $byVal,
    byRef     => $byRef,
} );

my $byVal2loc  = sub { my $v = ByVal2loc( $RECORD, $_) for 0..999 };
my $byRef2loc  = sub { my $v = ByRef2loc(\$RECORD, $_) for 0..999 };

sub ByVal2loc { my $arg = shift; return substr(  $arg, $_[0], 1) }
sub ByRef2loc { my $arg = shift; return substr( $$arg, $_[0], 1) }

timethese( $ARGV[0], {
    byVal2loc => $byVal2loc,
    byRef2loc => $byRef2loc,
} );

# Produces this output:
Benchmark: timing 10000 iterations of byRef, byVal, direct...
     byRef: 19 wallclock secs...
     byVal: 15 wallclock secs...
    direct:  4 wallclock secs...

Benchmark: timing 10000 iterations of byRef2loc, byVal2loc...
 byRef2loc: 21 wallclock secs...
 byVal2loc: 119 wallclock secs...

Wie erwartet, war die direkte Methode die schnellste. Allerdings war ich überrascht, dass es keine Nachteile durch das "Kopieren von Daten" gab, wie ich es mir vorgestellt hatte. Selbst als ich die Breite des Datensatzes auf haarsträubende Größenordnungen erhöhte (z. B. eine Milliarde Zeichen), waren die Benchmarks für die By-Value- und die By-Reference-Methode im Wesentlichen gleich.

Es scheint, dass Perl bei der Übergabe von Argumenten an Methoden keine Daten kopiert. Ich denke, das macht Sinn, wenn man weiter über die Aliasing-Kraft von @_ nachdenkt. Die Argumente werden per Referenz übergeben, nicht per Wert.

Es handelt sich jedoch um eine eingeschränkte Form der Weitergabe von Verweisen, da die Verweise in @_ nicht direkt einer lokalen Variablen innerhalb des Unterprogramms zugewiesen werden können. Solche Zuweisungen führen zu einem Kopieren von Daten, wie die zweite Reihe von Benchmarks zeigt.

Verstehe ich das richtig?

8voto

ysth Punkte 91645

Ja, Zuweisungen werden kopiert, die Übergabe von Argumenten jedoch nicht. Sie können lexikalische Elemente in @_ aliasieren, indem Sie Lexikalisch::Alias Allerdings. Dieser modifizierte Benchmark zeigt, dass dies nur ein Drittel so schnell geht wie bei der Verwendung einer Referenz, und zwar unabhängig von der Länge von $RECORD:

use strict;
use warnings;
use Benchmark qw(timethese);
use Lexical::Alias;

my $RECORD = '0' x 5000000;

my $byVal2loc  = sub { my $v = ByVal2loc( $RECORD, $_) for 0..999 };
my $byRef2loc  = sub { my $v = ByRef2loc(\$RECORD, $_) for 0..999 };
my $byAlias2loc = sub { my $v = ByAlias2loc( $RECORD, $_ ) for 0..999 };

sub ByVal2loc { my $arg = shift; return substr(  $arg, $_[0], 1) }
sub ByRef2loc { my $arg = shift; return substr( $$arg, $_[0], 1) }
sub ByAlias2loc { my $arg; alias($_[0], $arg); return substr( $arg, $_[0], 1  ) }

timethese( $ARGV[0], {
    byVal2loc => $byVal2loc,
    byRef2loc => $byRef2loc,
    byAlias2loc => $byAlias2loc,
} );

# output:
Benchmark: running byAlias2loc, byRef2loc, byVal2loc for at least 3 CPU seconds...
byAlias2loc:  3 wallclock secs ( 3.16 usr +  0.00 sys =  3.16 CPU) @ 430.70/s (n=1361)
 byRef2loc:  4 wallclock secs ( 3.24 usr +  0.00 sys =  3.24 CPU) @ 1329.63/s (n=4308)
 byVal2loc:  5 wallclock secs ( 4.95 usr +  0.01 sys =  4.96 CPU) @  0.40/s (n=2)
            (warning: too few iterations for a reliable count)

(Die direkte Verwendung von alias_r anstelle der alias-Hilfsfunktion ist geringfügig schneller).

6voto

Jonathan Leffler Punkte 694013

IIRC, in einem Perl-'sub', die @_ Array ist bereits ein Satz von Aliasen (Verweisen) auf die Variablen. Wenn Sie die $_[0] beeinflussen Sie die Variable in der aufrufenden Funktion.

#!/bin/perl -w
use strict;

sub x
{
    print "x = $_[0]\n";
    $_[0] = "pinkerton";
    print "x = $_[0]\n";
}

my $y = "abc";

print "y = $y\n";
x($y);
print "y = $y\n";

Die Ausgabe ist:

y = abc
x = abc
x = pinkerton
y = pinkerton

0 Stimmen

@Igor Krivokon: Richtig, ja, aber das stand schon in der Frage, zumindest implizit. Ich schätze, "Ja, Sie verstehen das richtig." fehlt etwas als Antwort.

0voto

zgpmax Punkte 2701

Wenn Sie den Elementen von @_ aussagekräftige Namen geben wollen, können Sie ihnen Aliase geben, indem Sie Daten::Alias also

use Data::Alias;

sub foo {
    alias my ($a, $b, $c) = @_;
}

Sie können ähnliche Dinge tun, indem Sie in Arrays und Hashes aliasing.

    alias my ($a, $b, @c) = @_;
    alias my ($a, $b, %c) = @_;

In der Tat, Aliasing in einen Hash

    alias my (%p) = @_;

ist besonders leistungsfähig, da es benannte Parameter für die Weitergabe von Referenzen bietet. Schön.

(Data::Alias bietet eine Obermenge der Funktionalität von Lexical::Alias; es ist allgemeiner einsetzbar und leistungsfähiger).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X