Software
Desarrollo
Herramienta

Nueve bibliotecas ingeniosas para perfilar el código de Python

Desde sencillos temporizadores y módulos de evaluación comparativa hasta sofisticados marcos basados en estadísticas, puedes recurrir a estas herramientas para conocer el rendimiento de tu programa Python.

reloj de arena, tiempo, retraso
Créditos: Aron Visuals (Unsplash).

Todo lenguaje de programación tiene dos tipos de velocidad: velocidad de desarrollo y velocidad de ejecución. Python siempre ha favorecido la escritura rápida frente a la ejecución rápida. Aunque el código Python es casi siempre lo suficientemente rápido para la tarea, a veces no lo es. En esos casos, hay que averiguar dónde y por qué se retrasa, y hacer algo al respecto.

Un adagio muy respetado del desarrollo de software, y de la ingeniería en general, es "Mide, no adivines". Con el software, es fácil suponer lo que está mal, pero nunca es una buena idea hacerlo. Las estadísticas sobre el rendimiento real de los programas son siempre la mejor herramienta para conseguir que las aplicaciones sean más rápidas.

La buena noticia es que Python ofrece toda una serie de paquetes que puedes utilizar para perfilar tus aplicaciones y saber dónde son más lentas. Estas herramientas van desde sencillos programas de una sola línea incluidos en la biblioteca estándar hasta sofisticados marcos de trabajo para recopilar estadísticas de las aplicaciones en ejecución. Aquí cubro nueve de las más significativas, la mayoría de las cuales se ejecutan en varias plataformas y están disponibles en PyPI o en la biblioteca estándar de Python.

 

Time y Timeit

A veces todo lo que necesitas es un cronómetro. Si todo lo que estás haciendo es perfilar el tiempo entre dos fragmentos de código que tardan segundos o minutos en ejecutarse, entonces un cronómetro será más que suficiente.

La biblioteca estándar de Python viene con dos funciones que funcionan como cronómetros. El módulo Time tiene la función perf_counter, que llama al temporizador de alta resolución del sistema operativo para obtener una marca de tiempo arbitraria. Llama a time.perf_counter una vez antes de una acción, una vez después, y obtén la diferencia entre las dos. Esto te proporciona una forma discreta y poco costosa, aunque también poco sofisticada, de cronometrar el código.

El módulo Timeit intenta realizar algo parecido a la evaluación comparativa real en el código de Python. La función timeit.timeit toma un fragmento de código, lo ejecuta muchas veces (el valor por defecto es 1 millón de pasadas), y obtiene el tiempo total requerido para hacerlo. Es mejor usarla para determinar cómo se comporta una sola operación o llamada a una función en un bucle cerrado, por ejemplo, si quiere determinar si la comprensión de una lista o una construcción de lista convencional será más rápida para algo hecho muchas veces. (Las listas de comprensión suelen ganar).

La desventaja de Time es que no es más que un cronómetro, y la desventaja de Timeit es que su principal caso de uso son los microcomprobadores de líneas individuales o bloques de código. Estos módulos sólo funcionan si se trata de código aislado. Ninguno de ellos es suficiente para el análisis de todo el programa, es decir, para averiguar en qué parte de las miles de líneas de código tu programa pasa la mayor parte de su tiempo.

 

cProfile

La biblioteca estándar de Python también viene con un perfilador de análisis de todo el programa, cProfile. Cuando se ejecuta, cProfile rastrea cada llamada a una función en su programa y genera una lista de las funciones que se llamaron con más frecuencia y el tiempo que las llamadas tomaron en promedio.

cProfile tiene tres grandes puntos fuertes. Uno, se incluye con la biblioteca estándar, por lo que está disponible incluso en una instalación de Python estándar. Dos, perfila una serie de estadísticas diferentes sobre el comportamiento de las llamadas; por ejemplo, separa el tiempo empleado en las instrucciones propias de una llamada, a una función del tiempo empleado por todas las demás llamadas invocadas por la función. Esto permite determinar si una función es lenta por sí misma o si llama a otras funciones que son lentas.

En tercer lugar, y quizás lo mejor de todo, puede restringir cProfile libremente. Puedes muestrear toda la ejecución de un programa, o puedes activar la creación de perfiles sólo cuando se ejecuta una función seleccionada, para centrarse mejor en lo que está haciendo esa función y en lo que está llamando. Este enfoque funciona mejor sólo después de haber reducido un poco las cosas, pero te ahorra la molestia de tener que atravesar el ruido de un seguimiento de perfil completo.

Lo que nos lleva al primer inconveniente de cProfile: genera muchas estadísticas de forma predeterminada. Tratar de encontrar la aguja correcta en todo ese heno puede ser abrumador. El otro inconveniente es el modelo de ejecución de cProfile: atrapa cada llamada de función, creando una cantidad significativa de sobrecarga. Esto hace que cProfile sea inadecuado para perfiles de aplicaciones en producción con datos en vivo, pero está perfectamente bien para crear perfiles durante el desarrollo.

 

FunctionTrace

FunctionTrace funciona como cProfile en sus líneas generales: le pasas el nombre del script que quieres perfilar, sin tener que añadir instrumentación al código, y genera un rastreo detallado de las llamadas a funciones y el uso de memoria a lo largo del tiempo. FunctionTrace también maneja aplicaciones multihilo/multiproceso sin que tengas que hacer nada extra.

Al igual que cProfile, FunctionTrace no utiliza el muestreo; cada acción se registra. Los componentes de perfilado están escritos en Rust para mayor velocidad. Los desarrolladores de FunctionTrace afirman que la sobrecarga de creacion de perfiles impuesta a las aplicaciones es inferior al 10%.

Los datos de rastreo se guardan en formato JSON, por lo que, en teoría, se puede utilizar cualquier aplicación para analizarlos. Pero la gran ventaja de FunctionTrace es que utiliza Firefox Profiler, que se ejecuta en cualquier navegador con JavaScript, no sólo en Firefox, para mostrar los resultados en un gráfico interactivo.

Ten en cuenta que los componentes de generación de perfiles de FunctionTrace aún no están disponibles en Windows; el perfilado sólo puede realizarse en sistemas Linux o Mac.

 

Palanteer

Una adición relativamente nueva al arsenal de perfiles de Python, Palanteer puede ser usado para perfilar tanto programas de Python como de C++. Esto lo hace muy útil si estás escribiendo una aplicación de Python que envuelve bibliotecas de C++ de tu propia creación, y quieres una visión más granular de ambos componentes de tu aplicación. Lo mejor de todo es que Palanteer muestra los resultados en una aplicación GUI que se ejecuta en el escritorio, y que se actualiza en directo mientras se ejecuta el programa.

Instrumentar las aplicaciones Python es tan sencillo como ejecutar la aplicación a través de Palanteer, de la misma manera que se utiliza cProfile. Las llamadas a funciones, las excepciones, la recolección de basura y las asignaciones de memoria a nivel del sistema operativo son todas registradas. Estos dos últimos son especialmente útiles si los problemas de rendimiento de tu aplicación resultan estar relacionados con el uso de la memoria o la asignación de objetos.

Una gran desventaja de Palanteer, al menos en este momento, es que debes construirlo completamente desde el código fuente. Todavía no hay binarios precompilados como ruedas de Python instalables, así que tendrás que sacar tu compilador de C++ y tener a mano una copia del código fuente de CPython.

 

Pyinstrument

Pyinstrument funciona como cProfile en el sentido de que rastrea tu programa y genera informes sobre el código que está ocupando la mayor parte de su tiempo. Pero Pyinstrument tiene dos grandes ventajas sobre cProfile que hacen que merezca la pena probarlo.

En primer lugar, Pyinstrument no intenta enganchar cada instancia de una llamada de función. Muestrea la pila de llamadas del programa cada milisegundo, por lo que es menos molesto pero lo suficientemente sensible como para detectar lo que está consumiendo la mayor parte del tiempo de ejecución de tu programa.

En segundo lugar, los informes de Pyinstrument son mucho más concisos. Te muestra las principales funciones de tu programa que consumen más tiempo, para que puedas centrarte en el análisis de los mayores culpables. También te permite encontrar esos resultados rápidamente, con poca ceremonia.

Pyinstrument también tiene muchas de las comodidades de cProfile. Puedes usar el perfilador como un objeto en tu aplicación, y registrar el comportamiento de funciones seleccionadas en lugar de toda la aplicación. La salida puede ser representada de varias maneras, incluso como HTML. Si quieres ver la línea de tiempo completa de las llamadas, también puedes exigirlo.

También hay que tener en cuenta dos advertencias. En primer lugar, algunos programas que utilizan extensiones compiladas en C, como los creados con Cython, pueden no funcionar correctamente cuando se invocan con Pyinstrument a través de la línea de comandos. Pero sí funcionan si Pyinstrument se utiliza en el propio programa, por ejemplo, envolviendo una función main() con una llamada Pyinstrument Profiler.

La segunda advertencia: Pyinstrument no funciona bien con el código que se ejecuta en varios subprocesos. Py-spy, detallado más adelante, puede ser la mejor opción en este caso.

 

Py-spy

Py-spy, al igual que Pyinstrument, funciona muestreando el estado de la pila de llamadas de un programa a intervalos regulares, en lugar de intentar registrar cada una de las llamadas. A diferencia de PyInstrument, Py-spy tiene componentes centrales escritos en Rust (Pyinstrument utiliza una extensión en C) y se ejecuta fuera de proceso con el programa perfilado, por lo que puede ser utilizado con seguridad con el código que se ejecuta en producción.

Esta arquitectura permite a Py-spy hacer fácilmente algo que muchos otros generadores de perfiles no pueden: perfilar aplicaciones Python multihilo o subprocesadas. Py-spy también puede perfilar extensiones de C, pero éstas necesitan ser compiladas con símbolos para ser útiles. Y en el caso de las extensiones compiladas con Cython, el archivo C generado necesita estar presente para reunir la información de rastreo adecuada.

Hay dos formas básicas de inspeccionar una aplicación con Py-spy. Puedes ejecutar la aplicación usando el comando de record de Py-spy, que genera un gráfico de llamas después de que la ejecución concluye. O puedes ejecutar la aplicación usando el comando top de Py-spy, que muestra una pantalla interactiva y actualizada de las entrañas de tu aplicación Python, mostrada de la misma manera que la utilidad top de Unix. Las pilas de subprocesos individuales también pueden ser volcadas desde la línea de comandos.

Py-spy tiene un gran inconveniente: Está pensado principalmente para perfilar un programa entero, o algunos componentes del mismo, desde el exterior. No te permite decorar y muestrear sólo una función en particular.

 

Snakeviz

La forma más común de visualizar los datos de una traza de cProfile es a través de otro módulo de la biblioteca estándar, pstats. El problema es que pstats genera informes de texto plano que no siempre proporcionan el tipo de visualización que se necesita para las estadísticas del perfil.

Snakeviz toma los datos generados por cProfile y produce gráficos interactivos fáciles de leer y renderizados con HTML. Hay dos tipos de gráficos disponibles: el "carámbano" y el "rayo de sol", cada uno de los cuales muestra de un vistazo dónde está ocupando su programa la mayor parte de su tiempo. Cada segmento del gráfico representa el tiempo de llamada de una función. Sólo tienes que hacer clic en un segmento para ampliar una función, y podrás inspeccionar el tiempo que tarda todo lo que hay en la pila por debajo de ella.

Snakeviz también genera una vista de tabla HTML de los datos de la traza que se puede buscar y ordenar; es como una versión más interactiva de las trazas creadas por pstats. Incluso si tú no te molestas con los gráficos, la vista de la tabla de la traza es por sí misma una manera muy poderosa de dar sentido a los datos de cProfile.

 

Yappi

Yappi ("Yet Another Python Profiler") tiene muchas de las mejores características de los otros perfiladores discutidos aquí, y algunas no proporcionadas por ninguno de ellos. PyCharm instala Yappi por defecto como su generador de perfiles de elección, por lo que los usuarios de ese IDE ya tienen acceso incorporado a Yappi.

Para usar Yappi, debes decorar tu código con instrucciones para invocar, iniciar, detener y generar informes para los mecanismos de perfilado. Yappi le permite elegir entre "tiempo de pared" o "tiempo de CPU" para medir el tiempo empleado. El primero es sólo un cronómetro; el segundo registra, a través de las APIs nativas del sistema, el tiempo que la CPU estuvo realmente ocupada en la ejecución del código, omitiendo las pausas por E/S o por hilos en espera. El tiempo de la CPU te da la sensación más precisa de cuánto tiempo tardan realmente ciertas operaciones, como la ejecución de código numérico.

Una ventaja muy buena de la forma en que Yappi maneja la recuperación de estadísticas de los hilos es que no tienes que decorar el código del hilo. Yappi proporciona una función, yappi.get_thread_stats(), que recupera las estadísticas de cualquier actividad de los subprocesos que registre, que luego puede analizar por separado. Las estadísticas pueden ser filtradas y ordenadas con alta granularidad, de forma similar a lo que se puede hacer con cProfile.

Finalmente, Yappi también puede perfilar greenlets y coroutines, algo que muchos otros perfiladores no pueden hacer fácilmente o en absoluto. Dado el creciente uso de metáforas asíncronas en Python, la capacidad de generar perfiles de código simultáneo es una herramienta poderosa.



Contenido Patrocinado

Forma parte de nuestra comunidad

 

¿Te interesan nuestras conferencias?

 

 
Cobertura de nuestros encuentros
 
 
 
 
Lee aquí nuestra revista de canal

DealerWorld Digital