Rendimiento en algoritmos o funciones En ocasiones he tenido la necesidad de medir cuánto tiempo tarda una función u operación dada en Python, y siempre acabo recurriendo al módulo time. He aquí un sencillo y representativo ejemplo de lo que estoy hablando: import time t0 = time.time() # O bien: t0 = time.clock() # Bucle con operación de ejemplo: for i in xrange(1000000): i += 1 print time.time()-t0 # O bien: # O bien: print time.clock()-t0 Usar una u otra función (time o clock) dependerá de gustos, puesto que de todos modos (la primera con más precisión, al menos en GNU/Linux), la salida de ambas indican el tiempo (en segundos) entre la primera y la segunda toma de muestra de tiempo. Evidentemente, este es un ejemplo pequeño, pero para operaciones más largas, es recomendable organizar el código a benchmarkear en una función y llamarla. También hay un módulo (timeit) que te evita alguna que otra linea de código y que está precisamente diseñado para estos menesteres, pero a pesar de rezar que uno no debe crear su propia función de medición de tiempo, tampoco he visto motivos para no hacerlo. Estos son otros ejemplos personales más ilustrativos/interesantes, que comparan distintos modos de hacer las cosas:
Rendimiento en aplicaciones Esto es útil para medir el rendimiento de funciones o algoritmos de modo aislado, pero no demasiado para aplicaciones medianamente complejas. Para estos casos, conviene tirar de profilers o perfiladores (esto no tiene nada que ver con hacerse las cejas). ¿Qué herramientas tenemos en Python para esto? Pues está el módulo profile (escrito en Python), cProfile (escrito en C), hotshot (el reemplazo de profile) y uno que he descubierto últimamente apto para aplicaciones multi-threaded: yappi. Veamos un ejemplo de preparación y uso de este último:
Una instanciación básica sería como la que sigue: import yappi def add10(): i = 0 for i in xrange(10): i += i yappi.start() # Iniciamos la monitorización de yappi add10() # Llamada a la funcion de ejemplo yappi.print_stats() # Mostramos las stats de ejecución yappi.stop() # Paramos la monitorización de yappi Y la salida constaría de 3 bloques identificables. La documentación que versa sobre la materia es concisa pero suficiente. El primer bloque son las estadísticas de las funciones, mostrando su nombre (name), el número de llamadas que realiza (#n), el tiempo de ejecución de la función sin contar las llamadas (tsub), el tiempo total de ejecución de la función (ttot) y el tiempo medio (ttot / #n = tavg). name #n tsub ttot tavg ..dist-packages/yappi.py.get_stats:5 1 0.000000 0.000000 0.000000 ..st-packages/yappi.py.print_stats:5 1 0.000000 0.000000 0.000000 test.py.add10:3 1 0.000023 0.000023 0.000023 El segundo bloque son las estadísticas del thread: nombre, id, nombre de la última función ejecutada por el hilo (fname), numero de veces que es llamado (scnt) y tiempo total de ejecución (ttot). name tid fname scnt ttot _MainThread -121.. ..dist-packages/yappi.py.get_stats:5 1 0.000000 El tercer y último bloque da información general del perfilado: estado del perfilador (status), cuando inició su ejecución (tstart), número total de funciones perfiladas (fcnt), número total de hilos perfilados (tcnt) y uso de memoria por parte del perfilador (mem), que no de la aplicación perfilada. status tstart fcnt tcnt mem(bytes) running Wed Mar 16 22:56:06 2011 4 1 45868 Explicado qué significa cada cosa que muestra, ¿qué significan los datos de salida de nuestro anterior ejemplo? En el primer bloque del perfilado de nuestra pequeña aplicación vemos que se ejecutan 3 funciones: 2 de ellas del propio perfilador y una nuestra, que es la que acapara prácticamente la totalidad del tiempo de ejecución. En el segundo bloque del perfilado, vemos que sólo lanzamos una vez el hilo principal de la aplicación (_MainThread) y la última función que lanzó fue la del perfilador (get_stats). En el tercer y último bloque vemos cuándo lanzamos el perfilado, que se perfilaron 4 funciones, 1 hilo y que el perfilador usó unos 45Kb. Referencias:
Comentarios (2) |