Guía de análisis de rendimiento de Python
Fuente de traducción | Red de código abierto de China
Aunque no todos los programas Python que escribe requieren un análisis de rendimiento riguroso, la buena noticia es que cuando surgen tales problemas, existen Hay varias herramientas en el ecosistema Python.
Analizar el rendimiento de un programa se reduce a responder 4 preguntas básicas:
1. ¿Cuántos bloques ejecutó el programa?
2. ¿Dónde está el cuello de botella de velocidad?
3. ¿Cuánta memoria utiliza?
4. ¿Dónde se produce la pérdida de memoria?
A continuación, responderemos estas preguntas en profundidad utilizando algunas herramientas interesantes.
Usando una herramienta de tiempo para tiempos aproximados
Primero, podemos usar una herramienta rápida y sucia para verificar el tiempo de ejecución de nuestro código: la antigua herramienta de Unix tiempo.
El significado de las tres variables de entrada anteriores se describe en detalle en el artículo de stackoverflow. En resumen:
real: representa el tiempo de ejecución real del programa
usuario: representa el tiempo total de CPU del programa en el estado de usuario
sys - representa el tiempo total de CPU del programa en estado de kernel
Al agregar los tiempos del sistema y del usuario, puede comprender visualmente los ciclos de CPU necesarios para que un programa se ejecute cuando no se están ejecutando otros programas en el sistema.
Si la suma del tiempo del sistema y el tiempo del usuario es mucho menor que el tiempo real, entonces se puede adivinar que el principal problema de rendimiento del programa puede estar relacionado con la espera de IO.
Sincronización detallada utilizando administradores de contexto de sincronización
Nuestra siguiente técnica implica instrucciones de código que acceden directamente a información de sincronización detallada. Aquí hay un pequeño fragmento de código que me pareció muy importante para usar mediciones de tiempo especializadas:
timer.py
Para usarlo, debes usar Python con la palabra clave y un administrador de contexto del temporizador para encapsular los bloques de código que desea cronometrar. Iniciará el temporizador cuando el bloque de código comience a ejecutarse y lo detendrá cuando finalice el bloque de código.
Aquí hay un ejemplo usando el fragmento de código anterior:
A menudo registro la salida de estos temporizadores en un archivo para poder observar cómo el rendimiento de mi programa cambia con el tiempo.
Usa el generador de perfiles para calcular el tiempo y la frecuencia de ejecución línea por línea
Robert Kern tiene un bonito proyecto llamado line_profiler que uso a menudo para ver la ejecución de cada línea de código en un código. velocidad y frecuencia del fragmento.
Para usarlo, necesitas instalar el paquete Python a través de pip:
Una vez instalado, utilizarás un nuevo módulo llamado "line_profiler" y un script ejecutable "kernprof.py". .
Para utilizar esta herramienta, primero modifica el código fuente y decora el decorador @profile en la función que deseas medir. No te preocupes, no necesitas importar ningún módulo. El script kernprof.py lo inyectará automáticamente en el tiempo de ejecución del script cuando se ejecute.
primes.py
Después de configurar el decorador @profile, use kernprof.py para ejecutar el script.
La opción -l le dice a kernprof que inyecte el decorador @profile en las funciones integradas del script, mientras que la opción -v le dice a kernprof que muestre un mensaje de tiempo cuando el script complete la ejecución. El resultado del script anterior se ve así:
Busque filas con un valor de clic alto o un valor de tiempo alto. Estas son las áreas que se pueden optimizar para lograr la mayor mejora.
¿Cuánta memoria utiliza el programa?
Ahora que entendemos mejor el tiempo, echemos un vistazo a cuánta memoria utiliza el programa. Fabián Pedregosa implementó un bonito perfilador de memoria modelado a partir del line_profiler de Robert Kern.
Primero use pip para instalarlo:
(Se recomienda instalar el paquete psutil porque puede mejorar en gran medida el rendimiento de Memory_profiler).
Al igual que line_profiler, Memory_profiler también debe decorarse con el decorador @profile en la parte superior de la función relevante:
Para ver cuánta memoria ocupa la función, ejecute lo siguiente:
p>
Una vez que salga el programa, verá el siguiente resultado:
>Teclas de acceso directo de IPython para line_profiler y Memory_profiler
Hay un pequeño -Truco conocido para Memory_profiler y line_profiler. Todos tienen comandos de acceso directo en IPython. Todo lo que necesitas hacer es escribir lo siguiente en tu sesión IPython:
Para hacerlo, necesitas acceso a los comandos mágicos %lprun y %mprun, que se comportan de manera similar al formulario de línea de comando. La principal diferencia es que no necesita usar @profiledecorator para modificar la función que desea perfilar. Simplemente ejecute el análisis directamente en su sesión de IPython como antes:
Esto le ahorrará mucho tiempo y esfuerzo ya que no necesita modificar su código fuente para usar estos comandos de análisis.
¿Dónde está la pérdida de memoria?
El intérprete de Python utiliza el recuento de referencias como método principal para registrar el uso de la memoria. Esto significa que cada objeto contiene un contador que se incrementa cuando una referencia a ese objeto se almacena en algún lugar y disminuye cuando se elimina la referencia. Cuando el contador llega a cero, el intérprete de cPython sabe que el objeto ya no se utiliza y lo elimina, liberando la memoria ocupada.
A menudo se producen pérdidas de memoria si las referencias a objetos que ya no se utilizan en el programa todavía están ocupadas.
La forma más rápida de encontrar este tipo de "pérdidas de memoria" es utilizar el objgraph de Marius Gedminas, una herramienta excelente. Esta herramienta le permite ver la cantidad de objetos en la memoria y encontrar la ubicación de todo el código que contiene referencias a ese objeto.
Para comenzar, primero instale objgraph:
Después de instalar la herramienta, inserte una línea en su código que llame al depurador:
Cuáles son los objetos más comunes ?
En tiempo de ejecución, puede ver los 20 objetos más populares de su programa ejecutando el siguiente comando:
¿Qué objetos se agregaron o eliminaron?
También podemos ver objetos que se agregaron o eliminaron entre dos momentos en el tiempo:
¿Quién tiene una referencia al objeto filtrado?
Más abajo, también puedes ver dónde está contenida una referencia a un objeto determinado. Tomemos el siguiente programa simple como ejemplo:
Para ver la ubicación que contiene la referencia a la variable x, ejecute la función objgraph.show_backref():
La salida de este comando debería La imagen PNG, guardada en /tmp/backrefs.png, tiene este aspecto:
En tiempo de ejecución, puede ver los 20 objetos más comunes en su programa ejecutando: con texto rojo en la parte inferior. box es el objeto de nuestro interés. Podemos ver que está referenciado una vez por el símbolo x y tres veces por la lista y. Si es x el que está causando la pérdida de memoria, podemos utilizar este método para comprobar por qué no se libera automáticamente rastreando todas sus referencias.
Recordemos que objgraph nos permite:
Mostrar los N principales objetos que ocupan la memoria de un programa Python
Mostrar qué objetos han sido eliminados después de un periodo de tiempo y qué objetos se añade
Muestra todas las referencias a un determinado objeto en un script
Esfuerzo y precisión
En este post te muestro cómo analizar Fuga de memoria. Te muestro cómo utilizar varias herramientas para analizar el rendimiento de programas Python.
Con estas herramientas y técnicas, puede obtener toda la información que necesita para rastrear la mayoría de las pérdidas de memoria en su programa Python e identificar sus cuellos de botella de velocidad.
Como ocurre con muchas otras perspectivas, realizar un análisis de desempeño significa equilibrar los objetivos de esfuerzo con la precisión objetiva. Si está confundido, implemente la solución más simple que satisfaga sus necesidades actuales.
Referencias
Desbordamiento de pila: explicación del tiempo
line_profiler
analizador de memoria_profiler)
objgraph p>
Fin.