¿Cómo se puede perfilar un script de Python?

El Proyecto Euler y otros concursos de codificación suelen tener un tiempo máximo de ejecución o la gente se jacta de lo rápido que corre su solución particular. Con python, a veces los enfoques son algo chapuceros, es decir, añadir código de sincronización a __main__.

¿Cuál es una buena forma de perfilar el tiempo de ejecución de un programa python?

Solución

Python incluye un perfilador llamado cProfile. No sólo da el tiempo total de ejecución, sino que también mide el tiempo de cada función por separado, y le dice cuántas veces se llamó a cada función, lo que hace que sea fácil determinar dónde debe hacer optimizaciones.

Puedes llamarlo desde tu código, o desde el intérprete, así:

import cProfile
cProfile.run('foo()')

Aún más útil, puede invocar el cProfile al ejecutar un script:

python -m cProfile myscript.py

Para hacerlo aún más fácil, hice un pequeño archivo por lotes llamado 'profile.bat':

python -m cProfile %1

Así que todo lo que tengo que hacer es ejecutar:

profile euler048.py

Y obtengo esto:

1007 function calls in 0.061 CPU seconds

Ordered by: standard name
ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    1    0.000    0.000    0.061    0.061 :1()
 1000    0.051    0.000    0.051    0.000 euler048.py:2()
    1    0.005    0.005    0.061    0.061 euler048.py:2()
    1    0.000    0.000    0.061    0.061 {execfile}
    1    0.002    0.002    0.053    0.053 {map}
    1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler objects}
    1    0.000    0.000    0.000    0.000 {range}
    1    0.003    0.003    0.003    0.003 {sum}

EDIT: Actualizado el enlace a un buen recurso de vídeo de PyCon 2013 titulado Python Profiling [También a través de YouTube](https://www.youtube.com/watch?v=QJwVYlDzAXs).

Comentarios (13)

Vale la pena señalar que el uso del perfilador sólo funciona (por defecto) en el hilo principal, y no obtendrá ninguna información de otros hilos si los utiliza. Esto puede ser un poco de un gotcha como es completamente no mencionado en la documentación del perfilador.

Si también quieres perfilar hilos, querrás mirar la función threading.setprofile() en la documentación.

También puedes crear tu propia subclase threading.Thread para hacerlo:

class ProfiledThread(threading.Thread):
    # Overrides threading.Thread.run()
    def run(self):
        profiler = cProfile.Profile()
        try:
            return profiler.runcall(threading.Thread.run, self)
        finally:
            profiler.dump_stats('myprofile-%d.profile' % (self.ident,))

y utilizar esa clase ProfiledThread en lugar de la estándar. Podría darte más flexibilidad, pero no estoy seguro de que merezca la pena, especialmente si estás usando código de terceros que no utilizaría tu clase.

Comentarios (6)

En el código fuente de Virtaal hay una clase y un decorador muy útiles que pueden facilitar la creación de perfiles (incluso para métodos/funciones específicas). El resultado se puede ver muy cómodamente en KCacheGrind.

Comentarios (1)