domingo, 28 de noviembre de 2010

Mejorando código

english version

En la última entrada del blog habiamos quedado en que los sprites de cocos 0.4.0 eran bastante mas lentos que los de pyglet 1.1.4. Veamos si se puede mejorar los sprites de cocos.  

Por dónde empezar ?
Python tiene de fábrica el modulo cProfile, asi que agrego unas pocas lineas en cada script para saber cuánto tiempo se gasta en cada parte del código.

Esto me deja con un montón de mediciones; para explorar el conjunto de estadisticas uso RunSnakeRun, un visualizador de estadisticas cProfile.
Lo que hace es dibujar el call tree en forma de cajas anidadas, con area proporcional al tiempo consumido.

Jugando un poco con las opciones de visualizacion obtengo un par de imágenes que parecen interesantes; combinadas en una y anotadas se ven así:



Me llama un poco la atención el área ocupada por lambdas y _set_position dentro de cocosnode.py

De dónde vienen los lambda ?
Los CocosNodes tienen varias properties como ser x, y, position, scale, rotation. Usando w como nombre genérico de una property, el código usando lambdas muestra un patrón de uso:

  • definir métodos _get_w y _set_w
  • definir la property como
La razón de usar éste estilo tiene que ver con herencia:
  • sin el lambda, si una subclase redefine _set_w, la property no vé el cambio salvo que redefinamos explicitamente con w = property(...)
  • con el lambda, si una subclase redefine _set_w, la subclase usará el nuevo _set_w para la property

Bueno, en relación a el problema de performace, podria ser que la indirección extra del lambda produjera el enlentecimiento ?
Tiempo de modificar el código y ver que pasa.

Primer intento
Hago un branch en el repositorio.
Empiezo a eliminar lambdas, y se vé que en algunos casos hay una pequeña mejora de rendimiento y el programa sigue comportándose bien; en otros casos hay una mejora grande pero las bolas no se mueven.
cocos Sprite hereda de CocosNode, Batchable y pyglet Sprite, hay properties y lambdas de por medio; el código en cada método es corto y simple, pero seguir todos los saltos es un poco confuso.
Decido consolidar explícitamente la lógica de cocos Sprite, o sea reemplazar los llamados a super y las asignaciones de propiedades; ésto permitirá al menos confirmar que mi modelo mental de lo que deberia ocurrir es correcto.
El programa funciona correctamente y el rendimiento es prácticamente el de pyglet Sprite.

Segundo intento
La solución anterior es rápida pero sucia: al consolidar el código perdemos la separación de responsabilidades entre las diferentes clases; cualquier cambio en las clases base habria que transportarlo manualmente a cocos Sprite.
De las diferentes etapas del experimento anterior pareceria que la pérdida de performance debida a los lambdas es poca, y que el problema estaria en que alguna parte mas pesada del código es llamada dos veces.
Lo que se precisa es ver exactamente que métodos son llamados.

sys.settrace al rescate Si se llama sys.settrace(mi_funcion_espia) en un programa, el intérprete python nos avisará cuando se producen eventos interesantes, como ser 'call', 'ret', 'line', y además en cada llamada nos pasa un objeto frame al cual le podemos preguntar cosas interesantes.
Lo que hay que hacer es usar mi_funcion_espia como un filtro que solo deje pasar las llamadas que interesan.
Francamente, de lo que se vé en los docs de python no sabria como hacerlo, pero viendo éste artículo se me aclararon las cosas.
Con ese artículo, los docs, algunos prints y dir(tal_cosa) escribo mi propio filtro.
Usándolo en el código original se confirma que en un lugar sospechoso hay una doble llamada; corrigiendo esto las bolas dejan de moverse pero el trace muestra qué metodo falla en llamar a otro. Ahora es fácil arreglar.
Mido el rendimiento a ver que pasa:


La solución limpia nos deja con aproximadamente el 90% del rendimiento de pyglet, bastante mejor que el 60% de cocos 0.4.0 .

edit:cocos 0.5.0 release ya usa el código mejorado.

No hay comentarios:

Publicar un comentario