2011-12-02 12 views
5

me gustaría saber qué es exactamente lo que sucede cuando se utiliza la reflexión para llamar a un método cuyo nombre tengo como una cadena:

my $foo = Foo->new(); 
my $method = 'myMethod'; 
$foo->$method(); 

es ~ 20% más lenta que la llamada nativa:

$foo->myMethod(); 

Cualquier punteros a documentación acerca de cómo la reflexión de perl se implementa sería de gran ayuda.

Gracias.

+3

Creo que es bastante obvio que en lugar de los códigos de operación que contienen la referencia a un método particular, necesita más instrucciones para buscar el método en la cadena de símbolos de la jerarquía '@ ISA' y enviarlo a ese en tiempo de ejecución. – Axeman

+1

@Axeman, el despacho del método es dinámico: considere la modificación del tiempo de ejecución '@ ISA' (o la tabla de símbolos), los objetos que se vuelven a convertir, etc. Además, [' perlobj'] (http://perldoc.perl.org/ perlobj.html) describe la búsqueda de métodos como runtime-cached, que a primera vista sugiere que la prueba '@ ISA' no puede explicar la diferencia de velocidad. – pilcrow

Respuesta

4

Primero, no confío en los puntos de referencia que no veo. Es muy fácil equivocarse. Los comparé yo mismo.

use strict; 
use warnings; 

use Benchmark qw(cmpthese); 

sub new { return bless({}, $_[0]); } 
sub myMethod { } 

my %tests = (
    rt => '$foo->$method() for 1..1000;', 
    ct => '$foo->myMethod() for 1..1000;', 
); 

$_ = 'use strict; use warnings; our $foo; our $method; ' . $_ 
    for values(%tests); 

our $foo = __PACKAGE__->new(); 
our $method = 'myMethod'; 

cmpthese(-3, \%tests); 

Puedo replicar los resultados.

 Rate rt ct 
rt 1879/s -- -19% 
ct 2333/s 24% -- 

(Rate is 1/1000th of actual rate.) 

Eso parece bastante grande, pero los porcentajes pueden ser muy engañosos con algo tan rápido. Veamos la diferencia en tiempos absolutos.

Compile-time: 2333000 calls per second = 429 nanoseconds per call 
Run-time:  1879000 calls per second = 532 nanoseconds per call 
Difference: 103 nanoseconds per call. 

No mucho. Entonces, ¿dónde está ese tiempo?

$ perl -MO=Concise,-exec -e'$foo->myMethod()'  $ perl -MO=Concise,-exec -e'$foo->$method()' 
1 <0> enter         = 1 <0> enter 
2 <;> nextstate(main 1 -e:1) v:{    = 2 <;> nextstate(main 1 -e:1) v:{ 
3 <0> pushmark s        = 3 <0> pushmark s 
4 <#> gvsv[*foo] s       = 4 <#> gvsv[*foo] s 
               + 5 <#> gvsv[*method] s 
5 <$> method_named[PV "myMethod"]    ! 6 <1> method K/1 
6 <1> entersub[t2] vKS/TARG     = 7 <1> entersub[t3] vKS/TARG 
7 <@> leave[1 ref] vKP/REFC     = 8 <@> leave[1 ref] vKP/REFC 
-e syntax OK         = -e syntax OK 

Parece que la única diferencia es una búsqueda de tablas de símbolos adicionales. 100ns parece excesivo para eso. Pero para estar seguro, compare con algo pequeño, digamos como agregar uno.

$ perl -MO=Concise,-exec -e'my $y = $x;'  $ perl -MO=Concise,-exec -e'my $y = $x + 1;' 
1 <0> enter        = 1 <0> enter 
2 <;> nextstate(main 1 -e:1) v:{   = 2 <;> nextstate(main 1 -e:1) v:{ 
3 <#> gvsv[*x] s       = 3 <#> gvsv[*x] s 
              + 4 <$> const[IV 1] s 
              + 5 <2> add[t3] sK/2 
4 <0> padsv[$y:1,2] sRM*/LVINTRO   = 6 <0> padsv[$y:1,2] sRM*/LVINTRO 
5 <2> sassign vKS/2      = 7 <2> sassign vKS/2 
6 <@> leave[1 ref] vKP/REFC    = 8 <@> leave[1 ref] vKP/REFC 
-e syntax OK        = -e syntax OK 

tapar ese código y our $x = 100; en el código de referencia más arriba, obtenemos

  Rate addition baseline 
addition 4839/s  --  -26% 
baseline 6532/s  35%  -- 

(Rate is 1/1000th of actual rate.) 

Así,

Basline: 6553000/s = 153 nanoseconds per assignment 
Addition: 4839000/s = 207 nanoseconds per assignment+addition 
Difference:    54 nanoseconds per addition 

lo tanto, es razonable para una simple búsqueda en la tabla de símbolos para tomar el doble de siempre y cuando agregue uno? Probablemente, ya que implica hash de una cadena y buscar una cadena en la lista de enlaces cortos.

¿Realmente te importa gastar 100ns extra aquí y allá? No, estoy adivinando.

+0

+1 para una "optimización prematura" de largo aliento! :-) – Tanktalus

10
> perl -MO=Concise -e '$foo->$bar' 
8 <@> leave[1 ref] vKP/REFC ->(end) 
1  <0> enter ->2 
2  <;> nextstate(main 1 -e:1) v:{ ->3 
7  <1> entersub[t3] vKS/TARG ->8 
3  <0> pushmark s ->4 
-  <1> ex-rv2sv sKM/1 ->5 
4   <#> gvsv[*foo] s ->5 
6  <1> method K/1 ->7    # ops to read $bar and then call method 
-   <1> ex-rv2sv sK/1 ->6  # 
5    <#> gvsv[*bar] s ->6  # 
-e syntax OK 

> perl -MO=Concise -e '$foo->bar' 
7 <@> leave[1 ref] vKP/REFC ->(end) 
1  <0> enter ->2 
2  <;> nextstate(main 1 -e:1) v:{ ->3 
6  <1> entersub[t2] vKS/TARG ->7 
3  <0> pushmark s ->4 
-  <1> ex-rv2sv sKM/1 ->5 
4   <#> gvsv[*foo] s ->5 
5  <$> method_named[PV "bar"] ->6 # op to call the 'bar' method 
-e syntax OK 

En el primer ejemplo, Perl tiene que cargar la variable $bar, y a continuación, comprobar para ver si contiene un nombre o un valor que se puede utilizar como un método. Dado que el contenido de $bar puede cambiar entre llamadas, esta búsqueda debe hacerse cada vez.

En el segundo ejemplo, perl ya sabe que la cadena "bar" se debe utilizar como nombre de método, por lo que se evita cargar la variable y verificar su contenido en cada ejecución.

Pero no debe preocuparse demasiado por una diferencia de velocidad del 20% entre dos operaciones nativas. Principalmente porque las operaciones nativas son muy rápidas, y cualquier velocidad que realmente necesiten será eclipsada rápidamente por el algoritmo real que su código tiene que realizar. En otras palabras, a menos que haya aislado este problema como un punto caliente con un generador de perfiles de código, la diferencia de velocidad tiene una importancia más pedagógica que práctica.

1

Se podría acelerar este proceso mediante el uso de un método de referencia, Ala:

$metref = \&{"Class::$method"}; 
$instance = new Class; 
$instance->$metref(@args); 

Se puede utilizar obviamente $metref = \&Class::myMethod lugar si sabía el nombre del método en tiempo de compilación. También hay cierres que usan sub { ... } que perl podría tratar más eficientemente que su desreferencia simbólica. Ver this perlmonks discussion y perlref : Making References.

+5

Mejor escribir 'Class :: -> can ($ method)' para obtener el valor de código. Respeta herencia y trabaja bajo estricto. Además, '$ metref -> ($ instancia, @args)' es aún más rápido. –

Cuestiones relacionadas