2009-09-07 7 views
7

Nel seguente modulo di esempio, i getter e i setter vengono generati aggiungendo subroutine anonime alla tabella dei simboli. Dopo che i metodi sono stati creati in questo modo, il codice risultante sarà funzionalmente equivalente (in termini di comportamento, velocità, ecc.) A un modulo con getter e setter scritti manualmente, oppure questo approccio ha una sorta di responsabilità intrinseca? (Ho fatto un po 'di analisi comparativa di velocità di base e non hanno rilevato alcuna differenza finora.)In Perl ci sono degli svantaggi nel generare getter e setter invece di codificarli con hard-coding?

package Module;  
use strict; 
use warnings; 

BEGIN { 
    my @attr = qw(author title number); 
    no strict 'refs'; 
    for my $a (@attr){ 
     *{__PACKAGE__ . "::get_$a"} = sub { $_[0]->{$a}   }; 
     *{__PACKAGE__ . "::set_$a"} = sub { $_[0]->{$a} = $_[1] }; 
    } 
} 

sub new { 
    my $class = shift; 
    bless { @_ }, $class; 
} 

1; 
+0

'* {" get_ $ a "} = sub' ... dovrebbe funzionare anche. (Non c'è bisogno di avere '__PACKAGE__' lì dentro) –

risposta

8

Non ci dovrebbe essere alcuna differenza in termini di prestazioni runtime se il codice risultante è lo stesso in entrambi i casi. Questo di solito non è possibile, tuttavia, a meno che non si usi la stringa eval per creare le subroutine. Per esempio, il codice è fornito:

... = sub { $_[0]->{$a} }; 

sarà sempre così leggermente più lento del codice che avrebbe scritto a mano:

sub foo { $_[0]->{'foo'} } 

semplicemente perché il primo ha per ottenere il valore della variabile $ a prima di usarla come chiave dell'hash, mentre la successiva usa una costante come chiave hash. Inoltre, a parte, shift di solito tende ad essere più veloce di $_[0].Ecco po 'di codice di riferimento:

use Benchmark qw(cmpthese); 

package Foo; 

sub manual_shift { shift->{'foo'} } 
sub manual_index { $_[0]->{'foo'} } 

my $attr = 'foo'; 

*dynamic_shift = sub { shift->{$attr} }; 
*dynamic_index = sub { $_[0]->{$attr} }; 

package main; 

my $o = bless { foo => 123 }, 'Foo'; 

cmpthese(-2, { 
    manual_shift => sub { my $a = $o->manual_shift }, 
    manual_index => sub { my $a = $o->manual_index }, 
    dynamic_shift => sub { my $a = $o->dynamic_shift }, 
    dynamic_index => sub { my $a = $o->dynamic_index }, 
}); 

ed i risultati sul mio sistema:

    Rate dynamic_index manual_index dynamic_shift manual_shift 
dynamic_index 1799024/s   --   -3%   -4%   -7% 
manual_index 1853616/s   3%   --   -1%   -4% 
dynamic_shift 1873183/s   4%   1%   --   -3% 
manual_shift 1937019/s   8%   4%   3%   -- 

Sono così vicino che le differenze possono perdersi nel rumore, ma tra le tante prove penso che vedrete che la variante "shift manuale" è la più veloce. Ma come con tutti i microbenchmarks come questo, devi verificare il tuo scenario esatto sul tuo hardware e la tua versione di perl per essere sicuro di tutto.

E qui è eval stringa gettata nel mix.

eval "sub eval_index { \$_[0]->{'$attr'} }"; 
eval "sub eval_shift { shift->{'$attr'} }"; 

Dovrebbe essere esattamente uguali alle varianti "manuale", più o meno il rumore statistico. I miei risultati:

    Rate dynamic_index manual_index dynamic_shift manual_shift eval_shift eval_index 
dynamic_index 1820444/s   --   -1%   -2%   -3%  -4%  -5% 
manual_index 1835005/s   1%   --   -1%   -2%  -3%  -4% 
dynamic_shift 1858131/s   2%   1%   --   -1%  -2%  -3% 
manual_shift 1876708/s   3%   2%   1%   --  -1%  -2% 
eval_shift 1894132/s   4%   3%   2%   1%   --  -1% 
eval_index 1914060/s   5%   4%   3%   2%   1%   -- 

Anche in questo caso, questi sono tutti così vicini che avresti dovuto prendere grandi dolori ed eseguire molte prove per risolvere il segnale dal rumore. Ma la differenza tra l'utilizzo di una costante come chiave hash e l'uso di una variabile (il cui valore deve essere prima richiamato) come una chiave di hash dovrebbe essere visibile. (L'ottimizzazione shift è un problema separato ed è più probabile che cambi in un modo o nell'altro nelle versioni precedenti o future di perl.)

+0

il tuo eval_index ha bisogno di $ in $ _ [0] escape. – ysth

+1

Se hai intenzione di generare accessor, potresti anche usare un generatore FAST. Cf. http://search.cpan.org/dist/Class-XSAccessor Questo sarà più veloce di qualunque altro accesso diretto all'hash. – tsee

+0

ysth: Grazie, penso che il backslash sia stato mangiato mentre si modificava localmente. L'ho corretto –

7

Non v'è alcuna differenza perché:

sub Some_package::foo { ... } 

è solo una scorciatoia per:

BEGIN { *Some_package::foo = sub { ... } } 

Riferimento da perlmod

2

Entrambi gli approcci hanno il risultato di installare un sub riferimento di routine nella tabella dei simboli in fase di compilazione. Il comportamento e le prestazioni di runtime saranno esattamente le stesse. Potrebbe esserci una differenza molto piccola (cioè trascurabile) nel tempo di compilazione.

Un approccio simile consiste nel generare accessor su richiesta tramite AUTOLOAD, che ha un impatto minimo in fase di esecuzione. L'utilizzo dell'approccio AUTOLOAD può anche modificare il comportamento di cose come $object->can().

Ovviamente, la generazione di metodi li nasconde da qualsiasi forma di analisi statica, inclusi strumenti come ctags.

+1

Seriamente. Non usare AUTOLOAD per cose del genere. Apre le porte dell'inondazione alla follia. (Sono abbastanza sicuro che tu, MC, lo sappia :) – tsee

+3

** 'ATTENZIONE:' ** Non usare 'AUTOLOAD' a meno che tu non sappia ** esattamente ** cosa stai facendo. –

2

Il comportamento e le prestazioni di runtime dovrebbero essere pressoché gli stessi (a meno che non si faccia qualcosa che si preoccupi che i metodi siano o meno chiusi).

Con un numero elevato di attributi, ci sarà una differenza nel tempo di compilazione e nell'uso della memoria ... sia a favore dei getter e setter generati, non di quelli scritti manualmente. Provate, per esempio, questo:

BEGIN { 
    no strict 'refs'; 
    for my $a ("aaaa".."zzzz"){ 
     *{__PACKAGE__ . "::get_$a"} = sub { $_[0]->{$a}   }; 
     *{__PACKAGE__ . "::set_$a"} = sub { $_[0]->{$a} = $_[1] }; 
    } 
} 
print `ps -F -p $$`; # adjust for your ps syntax 

rispetto al

sub get_aaaa { $_[0]->{aaaa}   } 
sub set_aaaa { $_[0]->{aaaa} = $_[1] } 
sub get_aaab { $_[0]->{aaab}   } 
... 
sub set_zzzy { $_[0]->{zzzy} = $_[1] } 
sub get_zzzz { $_[0]->{zzzz}   } 
sub set_zzzz { $_[0]->{zzzz} = $_[1] } 
print `ps -F -p $$`; # adjust for your ps syntax 
2

L'unica differenza è l'ora di avvio. Per schemi di generazione di codice semplici, la differenza sarà difficile da misurare. Per sistemi più complessi, può sommarsi.

Un grande esempio di questo in azione è Moose. Moose produce tutti i tipi di generazione di codice sorprendente per te, ma ha un impatto significativo sui tempi di avvio. Questo è abbastanza di un problema che gli sviluppatori di Moose stanno lavorando su a scheme to cache generated code in file pmc e li carica invece di rigenerare il codice ogni volta.

Considerare anche qualcosa come Class::Struct. Fa la generazione del codice usando la stringa eval (l'ultima volta che ho controllato). Anche così, perché è molto semplice, non provoca un rallentamento significativo all'avvio.

6

Lo svantaggio principale degli accessor generati è che sconfiggono strumenti basati su analisi statiche. Ad esempio, il completamento automatico del metodo IDE. Se questo fa parte di un grande progetto, raccomando vivamente di dare un'occhiata a Moose. È una generazione di accessor fatta bene (e molto altro). È abbastanza popolare che venga aggiunto il supporto agli IDE, in modo che il problema sopra menzionato scompaia a tempo debito.

Ci sono molti generatori di accesso su CPAN che sono facili da usare e generano codice moderatamente efficiente. Se le prestazioni sono un problema, allora - a patto di attenersi all'utilizzo dei metodi di accesso - non è possibile ottenere più rapidamente di Class::XSAccessor poiché utilizza un codice C/XS altamente ottimizzato per gli accessor.

Il rollover del proprio codice di accesso è la peggiore di tutte le opzioni. Sconfigge per sempre l'analisi statica, è probabile che sia piuttosto difficile da leggere e potenzialmente introduce nuovi bug.

2

A parte i punti eccellenti che gli altri hanno menzionato, vorrei anche aggiungere lo svantaggio principale che ho riscontrato: tutti appaiono come subroutine anonime nel profiler. Per qualsiasi motivo, Devel::DProf non sa come capire il nome.

Ora vorrei speranza che il nuovo Devel::NYTProf potrebbe fare un lavoro migliore - ma non l'ho provato.