Cómo usar cProfile para perfilar el código Python

Puede que Python no sea el lenguaje más rápido, pero a menudo es lo suficientemente rápido. Y Python es ideal cuando el tiempo del programador importa más que el tiempo de la CPU.

Dicho esto, si una aplicación de Python determinada está retrasada, no está obligado a aguantarla. Las herramientas incluidas con la instalación estándar del intérprete de Python pueden proporcionarle comentarios detallados sobre qué partes de su programa son lentas y ofrecer algunas sugerencias sobre cómo acelerarlas.

Cómo utilizar cProfile

El cProfilemódulo recopila estadísticas sobre el tiempo de ejecución de un programa Python. Puede informar sobre cualquier cosa, desde toda la aplicación hasta una sola declaración o expresión.

Aquí hay un ejemplo de juguete de cómo usar cProfile:

def add (x, y): x + = str (y) return x def add_2 (x, y): if y% 20000 == 0: z = [] para q en el rango (0,400000): z.append ( q) def main (): a = [] para n en el rango (0,200000): add (a, n) add_2 (a, n) if __name__ == '__main__': import cProfile cProfile.run ('main ( ) ') 

Este ejemplo ejecuta la main()función de la aplicación y analiza el rendimiento main()y todas las main()llamadas. También es posible analizar solo una  parte de un programa, pero el uso más común para los principiantes es perfilar todo el programa.

Ejecute el ejemplo anterior y será recibido con algo como el siguiente resultado:

Lo que se muestra aquí es una lista de todas las llamadas a funciones realizadas por el programa, junto con estadísticas sobre cada una:

  • En la parte superior (primera línea en azul), vemos el número total de llamadas realizadas en el programa perfilado y el tiempo total de ejecución. También puede ver una figura para "llamadas primitivas", es decir, llamadas no recursivas o llamadas realizadas directamente a una función que, a su vez, no se llaman a sí mismas más abajo en la pila de llamadas.
  • ncalls : número de llamadas realizadas. Si ve dos números separados por una barra, el segundo número es el número de llamadas primitivas para esa función.
  • tottime : tiempo total empleado en la función, sin incluir las llamadas a otras funciones.
  • percall : tiempo promedio por llamada para tottime , que se obtiene tomando tottime y dividiéndolo por ncalls .
  • cumtime : tiempo total empleado en la función, incluidas las llamadas a otras funciones.
  • percall (# 2): tiempo promedio por llamada para cumtime ( cumtime dividido por ncalls ).
  • filename: lineno : el nombre del archivo, el número de línea y el nombre de la función para la llamada en cuestión.

Cómo modificar los informes de cProfile

De forma predeterminada, cProfileordena su salida por "nombre estándar", lo que significa que ordena por el texto en la columna de la derecha (nombre de archivo, número de línea, etc.).

El formato predeterminado es útil si desea un informe general de arriba hacia abajo de cada llamada de función como referencia. Pero si está tratando de llegar al fondo de un cuello de botella, es probable que desee enumerar primero las partes del programa que más tiempo requieren.

Podemos producir estos resultados invocando de forma  cProfile un poco diferente. Observe cómo la parte inferior del programa anterior se puede reelaborar para ordenar las estadísticas por una columna diferente (en este caso ncalls):

if __name__ == '__main__': importar cProfile, pstats profiler = cProfile.Profile () profiler.enable () main () profiler.disable () stats = pstats.Stats (profiler) .sort_stats ('ncalls') stats.print_stats () 

Los resultados se verán así:

Así es como funciona todo esto:

  • En lugar de ejecutar un comando a través de cProfile.run(), lo que no es muy flexible, creamos un perfilado objeto , profiler.
  • Cuando queremos perfilar alguna acción, primero llamamos .enable()a la instancia del objeto generador de perfiles, luego ejecutamos la acción y luego llamamos .disable(). (Esta es una forma de perfilar solo una parte de un programa).
  • El pstatsmódulo se utiliza para manipular los resultados recopilados por el objeto generador de perfiles e imprimir esos resultados.

La combinación de un objeto de generador de perfiles y pstatsnos permite manipular los datos de perfil capturados, por ejemplo, para ordenar las estadísticas generadas de manera diferente. En este ejemplo, el uso .sort_stats('ncalls')ordena las estadísticas por ncallscolumna. Hay otras opciones de clasificación disponibles.

Cómo utilizar los resultados de cProfile para la optimización

Las opciones de clasificación disponibles para la cProfile salida nos permiten detectar posibles cuellos de botella de rendimiento en un programa.

ncalls

La primera y más importante información con la que puede desenterrar cProfilees qué funciones se llaman con más frecuencia, a través de la ncallscolumna.

En Python, el mero hecho de realizar una llamada a una función genera una sobrecarga relativamente grande. Si se llama a alguna función repetidamente en un bucle cerrado, incluso si no es una función de larga duración, se garantiza que afectará el rendimiento.

En el ejemplo anterior, la función add(y la función add_2) se llama repetidamente en un bucle. Mover el bucle a la addfunción en sí, o incluir la addfunción por completo, solucionaría este problema.

tottime

Otra estadística útil detalla las funciones que el programa dedica la mayor parte de su tiempo a ejecutar, a través de la tottimecolumna.

En el ejemplo anterior, la add_2función usa un bucle para simular algunos cálculos costosos, lo que lleva su tottimepuntaje a la cima. Cualquier función con una tottimepuntuación alta merece una mirada de cerca, especialmente si se llama muchas veces o en un bucle cerrado.

Tenga en cuenta que siempre debe considerar el contexto  en el que se utiliza la función. Si una función tiene un valor alto tottimepero solo se llama una vez, por ejemplo, solo cuando se inicia el programa, es menos probable que sea un cuello de botella. Sin embargo, si está tratando de reducir el tiempo de inicio, querrá saber si una función llamada al inicio hace que todo lo demás espere.

Cómo exportar datos de cProfile

Si desea utilizar cProfilelas estadísticas generadas de forma más avanzada, puede exportarlas a un archivo de datos:

stats = pstats.Stats (generador de perfiles) stats.dump_stats ('/ ruta / a / archivo_estad.dat') 

Este archivo se puede volver a leer utilizando el pstatsmódulo, luego se puede ordenar o mostrar con pstats. Los datos también pueden ser reutilizados por otros programas. Dos ejemplos:

  • pyprof2calltreepresenta visualizaciones detalladas del gráfico de llamadas del programa y las estadísticas de uso de los datos del perfil. Este artículo proporciona un ejemplo detallado del mundo real de su uso.
  • snakeviztambién genera visualizaciones a partir de los cProfiledatos, pero utiliza una representación diferente para los datos: un “sunburst” en lugar del gráfico de “llama” de pyprof2calltree.

Más allá de cProfile para la creación de perfiles de Python

cProfileno es la única forma de perfilar una aplicación Python. cProfilees sin duda una de las formas más convenientes, dado que se incluye con Python. Pero otros merecen atención.

Un proyecto, py-spycrea un perfil para una aplicación Python al muestrear su actividad de llamada. py-spyse puede usar para examinar una aplicación Python en ejecución sin tener que detenerla y reiniciarla, y sin tener que alterar su código base, por lo que se puede usar para perfilar aplicaciones implementadas. py-spytambién genera algunas estadísticas sobre la sobrecarga incurrida por el tiempo de ejecución de Python (por ejemplo, sobrecarga de recolección de basura), que cProfileno es así.