[Madrid-pm] Optimizando el valor de vuelta de una subrutina

Salvador Fandino sfandino en yahoo.com
Lun Mayo 28 14:00:53 PDT 2012








>________________________________
> From: JJ Merelo <jjmerelo en gmail.com>
>To: Lista de correo de Madrid Perl Mongers <madrid-pm en pm.org> 
>Sent: Sunday, May 27, 2012 3:13 PM
>Subject: [Madrid-pm] Optimizando el valor de vuelta de una subrutina
> 
>
>Hola
>Pregunta corta: ¿Cómo se hace para que Perl no devuelva un valor de una subrutina?
>Pregunta larga: en el MasterMind hay subrutinas que se llaman millones (o miles de millones) de veces. El profiler dice que, curiosamente, el devolver el valor de la misma es el cuello de botella. Igual hay otra forma de solucionarlo, como meterlas inline (la verdad, no sé como hacerlo) pero lo que he hecho ha sido declarar el prototipo como que devuelvan void y hacer que se les pase el hashref de vuelta como parámetro. Marginalmente mejora algo, pero sigue devolviendo el valor devuelto por la última función en la subrutina (un map). Añadir return; no mejora prácticamente nada, y declararla como void con prototipo tampoco. ¿Alguna idea?
>


No te creas lo que te dice tu profiler!!!
Cuando se activa en Perl el profiling, lo que el interprete hace es llamar a la subrutina de profiling/debugging (DB::db) cada vez que se va a ejecutar una nueva sentencia. Normalmente, lo que se hace dentro de esta subrutina, es cronometrar el tiempo transcurrido entre llamadas sucesivas y atribuir este a la penúltima operación. Pero a veces, hay operaciones implícitas para las que no se llama DB::db entremedias y esto es lo que esta pasando en el caso que planteas. Aqui la operacion oculta es la de abandonar una subrutina (leavesub) donde se liberan las variables intermedias, se deshacen las local'izaciones, se limpia el pad, etc.

"return" es la ultima llamada que se ejecuta dentro de una subrutina, y lo único que hace es meter en la pila uno o varios valores (según el contexto), es una operación muy barata.

Por ejemplo:

  sub foo {
    return 84;
  }

  $a=0;
  foo();
  $a+=1;

Lo que el profiler ve:

  $a=0;        #  se empieza a ejecutar en t0, consume t1 - t0
  return 84;  #  se empieza a ejecutar en t1, consume t2 - t1
  $a+=1;     #  se empieza a ejecutar en t2, consume t3 - t2
                 #  se acaba en t3

Y con B::Concise se puede ver lo que perl ejecuta realmente:

La parte correspondiente al codigo que no esta dentro de ninguna subrutina:

  $ perl -MO=Concise,-exec,-src /tmp/p.pl 
  1  <0> enter 
  # 5: $a=1;
  2  <;> nextstate(main 2 p.pl:5) v:{
  3  <$> const[IV 1] s
  4  <#> gvsv[*a] s
  5  <2> sassign vKS/2
  # 6: foo();
  6  <;> nextstate(main 2 p.pl:6) v:{
  7  <0> pushmark s
  8  <#> gv[*foo] s
  9  <1> entersub[t4] vKS/TARG,1
  # 7: $a+=1;
  a  <;> nextstate(main 2 p.pl:7) v:{
  b  <#> gvsv[*a] s
  c  <$> const[IV 1] s
  d  <2> add[t6] vKS/2
  e  <@> leave[1 ref] vKP/REFC

Y el desemsamblado de foo:

  $ perl -MO=Concise,-exec,-src,foo /tmp/p.pl 
  main::foo:
  # 2:     return 84;
  1  <;> nextstate(main 1 p.pl:2) v
  2  <0> pushmark s
  3  <$> const[IV 84] s
  4  <@> return K
  5  <1> leavesub[1 ref] K/REFC,1    <==== Aquí es donde va el tiempo oculto!

Por cierto, las llamadas a DB::db se hacen si no recuerdo mal en cada OP "nextstate".

Conclusión: lo que estas midiendo es parte de la sobrecarga implícita de llamar a una subrutina y no hay forma de eliminarla otra que no sea insertar el código de la subrutina directamente en el punto de llamada (un "inline", vamos), pero como perl no soporta macros, esto no se puede hacer de manera automatica (bueno, creo que en las ultimas versiones si con C / XS, utilizando algunas de las nuevas APIs, pero no parece nada fácil).


Más información sobre la lista de distribución Madrid-pm