2009-09-07 20 views
7

En el siguiente módulo de ejemplo, los getters y setters se generan agregando subrutinas anónimas a la tabla de símbolos. Después de que los métodos se hayan creado de esta manera, ¿el código resultante será funcionalmente equivalente (en términos de comportamiento, velocidad, etc.) a un módulo con captadores y establecedores escritos a mano, o tiene este tipo de algún tipo de responsabilidad inherente? (He hecho un poco de la evaluación comparativa de velocidad básica y no han detectado ninguna diferencia hasta el momento.)En Perl ¿hay desventajas para generar getters y setters en lugar de hard-coding them?

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' ... también debería funcionar. (No es necesario tener '__PACKAGE__' allí) –

Respuesta

8

No debería haber ninguna diferencia en el rendimiento en tiempo de ejecución si el código resultante es la misma en ambos casos. Sin embargo, esto generalmente no es posible, a menos que use la cadena eval para crear sus subrutinas. Por ejemplo, el código que proporciona:

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

habrá siempre tan ligeramente más lento que el código que habría escrito manualmente:

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

simplemente porque el primero tiene que obtener el valor de la variable $ a antes de usarlo como una clave para el hash, mientras que el último usa una constante como su clave hash. Además, como un lado, shift generalmente tiende a ser más rápido que $_[0].Aquí hay un código de referencia:

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

y los resultados en mi 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%   -- 

Están tan cerca que las diferencias pueden perderse en el ruido, pero a lo largo de muchos ensayos creo que se verá que la variante del "cambio manual" es la más rápida. Pero como con todos los microbenchmarks como este, tienes que probar tu escenario exacto en tu hardware y tu versión de Perl para estar seguro de cualquier cosa.

Y aquí está la eval de cadena arrojada a la mezcla.

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

Debe ser exactamente igual a las variantes "manuales", más o menos el ruido estadístico. Mis resultados:

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

Una vez más, estos son todos tan cerca que habría que tomar grandes dolores y realizar muchas pruebas para ordenar la señal del ruido. Pero la diferencia entre usar una constante como una clave hash y usar una variable (cuyo valor primero debe recuperarse) como una clave hash debería mostrarse a través de. (La optimización shift es un problema aparte y es más probable que cambie de una forma u otra en versiones pasadas o futuras de perl.)

+0

su eval_index necesita el $ in $ _ [0] escapado. – ysth

+1

Si va a generar accesórios, también podría utilizar un generador FAST. Cf. http://search.cpan.org/dist/Class-XSAccessor Eso será más rápido que cualquier cosa que no sea el acceso directo a hash. – tsee

+0

ysth: Gracias, creo que la barra invertida se comió mientras se editaba localmente. Lo he corregido –

7

No hay diferencia porque:

sub Some_package::foo { ... } 

es simplemente una abreviatura para:

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

Referencia de perlmod

2

Ambos enfoques tienen el resultado de instalar un submarino referencia de rutina en la tabla de símbolos en tiempo de compilación. El comportamiento y el rendimiento del tiempo de ejecución serán exactamente los mismos. Puede haber una diferencia muy pequeña (es decir, insignificante) en el tiempo de compilación.

Un enfoque similar es generar accesos bajo demanda a través de AUTOLOAD, que tiene un pequeño impacto en el tiempo de ejecución. Usar el enfoque AUTOLOAD también puede cambiar el comportamiento de cosas como $object->can().

Obviamente, la generación de métodos los ocultará de cualquier forma de análisis estático, incluidas herramientas como ctags.

+1

En serio. No use AUTOLOAD para tales cosas. Abre las compuertas a la locura. (Estoy bastante seguro de que usted, MC, sabe que :) – tsee

+3

** 'ADVERTENCIA:' ** No use 'AUTOLOAD' a menos que sepa ** exactamente ** lo que está haciendo. –

2

El comportamiento y el rendimiento del tiempo de ejecución deberían ser prácticamente los mismos (a menos que haga algo que le importe si los métodos son cierres o no).

Con un gran número de atributos, habrá una diferencia en el tiempo de compilación y en el uso de la memoria ... tanto a favor de los get y setters generados, no de los escritos manualmente. Pruebe, por ejemplo, esto:

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 

comparación con

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

La única diferencia es el tiempo de inicio. Para esquemas simples de generación de código, la diferencia será difícil de medir. Para sistemas más complejos, puede sumarse.

Un gran ejemplo de esto en acción es Moose. Moose hace todo tipo de increíble generación de código para ti, pero tiene un impacto significativo en los tiempos de puesta en marcha. Este es un problema suficiente para que los desarrolladores de Moose estén trabajando en a scheme to cache generated code en archivos pmc y los carguen en lugar de regenerar el código cada vez.

Considera también algo así como Class::Struct. Genera código usando eval de cadena (la última vez que lo comprobé). Aun así, debido a que es muy simple, no causa una desaceleración significativa en el arranque.

6

El inconveniente principal de los accesadores bien generados es que eliminan las herramientas que se basan en el análisis estático. Por ejemplo, el autocompletado del método IDE. Si esto es parte de un gran proyecto, recomiendo sinceramente que eche un vistazo al Moose. Se trata de una generación de acceso correcta (y mucho más). Es lo suficientemente popular como para que se agregue soporte a los IDEs, de modo que el problema mencionado desaparecerá a su debido tiempo.

Hay muchos generadores de accesorios en CPAN que son fáciles de usar y generan un código moderadamente eficiente. Si el rendimiento es un problema, entonces, siempre que se adhiera al uso de métodos de acceso, no se puede obtener más rápido que Class::XSAccessor, ya que utiliza un código C/XS altamente optimizado para los usuarios.

Rolar su propio código de generación de acceso es la peor de todas las opciones. Derrota el análisis estático para siempre, es bastante difícil de leer y potencialmente introduce nuevos errores.

2

Aparte de los excelentes puntos que los otros han mencionado, también me gustaría agregar la principal desventaja que encontré: todos aparecen como subrutinas anónimas en el generador de perfiles. Por alguna razón, Devel::DProf simplemente no sabe cómo descubrir el nombre.

Ahora me la esperanza de que la nueva Devel::NYTProf puede hacer un mejor trabajo - pero no lo han intentado.

Cuestiones relacionadas