2009-06-15 14 views
8

Ho lavorato su diversi script Perl che elaborano file di dati a larghezza fissa di grandi dimensioni, estrapolando piccole sottostringhe da ogni record di dati. Avevo immaginato che la delega dell'estrazione delle sottostringhe alle chiamate ai metodi sarebbe costosa a causa del sovraccarico di copia del record di dati nell'array @_. Così ho eseguito quanto segue per confrontare (a) chiamata diretta a substr(), (b) chiamata di metodo passando il record di dati come stringa, e (c) chiamata di metodo passando il record di dati per riferimento.Si verifica una perdita di prestazioni durante la copia dei dati quando si passano gli argomenti alle subroutine di Perl?

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... 

Come previsto, il metodo diretto è stato il più veloce. Tuttavia, sono stato sorpreso di non trovare penalità legate alla "copia di dati" che avevo immaginato. Anche quando aumentavo la larghezza del record in proporzioni stravaganti (ad esempio, un miliardo di caratteri), i benchmark in base al valore e al riferimento erano sostanzialmente gli stessi.

Sembra che quando si passano gli argomenti ai metodi, Perl non copia i dati. Immagino che questo abbia un senso dopo un'ulteriore riflessione sul potere di aliasing di @_. Gli argomenti vengono passati per riferimento, non per valore.

Tuttavia, è una forma limitata di passaggio per riferimento, poiché i riferimenti in @_ non possono essere assegnati direttamente a una variabile locale all'interno della subroutine. Tali assegnazioni comportano la copia dei dati, come illustrato dal secondo set di benchmark.

Sto comprendendo questo correttamente?

risposta

8

Sì, le assegnazioni di copia; solo gli argomenti di passaggio non lo fanno. Puoi alias i lessici per gli elementi in @_ utilizzando Lexical::Alias, tuttavia. Questo benchmark modificato mostra farlo una terza più velocemente utilizzando un riferimento, ma sempre in modo indipendentemente dalla lunghezza di $ 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) 

(. Direttamente usando alias_r anziché la funzione alias helper è marginalmente più veloce)

6

IIRC, in un Perl 'sub', l'array @_ è già un set di alias (riferimenti) alle variabili. Se si modifica $_[0], si influisce sulla variabile nella funzione di chiamata.

#!/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"; 

L'output è:

y = abc 
x = abc 
x = pinkerton 
y = pinkerton 
+0

+1. Questa è la risposta corretta. –

+0

@Igor Krivokon: corretto, sì, ma già indicato nella domanda, almeno implicitamente. Immagino "Sì, lo stai capendo correttamente."manca qualcosa come risposta. – ysth

0

Se si vuole dare gli elementi di @_ nomi significativi, è possibile effettuare gli alias a loro utilizzando Data::Alias, così

use Data::Alias; 

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

si possono fare cose simili aliasing in array e hash.

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

Infatti, aliasing in un hash

alias my (%p) = @_; 

è particolarmente potente in quanto fornisce pass-by-reference parametri denominati. Bello.

(dati :: Alias ​​offre un superset delle funzionalità di lessicale :: Alias, è più general purpose e più potente.)

Problemi correlati