Perfilando el código de Python utilizando timeit y cProfile
Optimizing Python code with timeit and cProfile
Como desarrollador de software, es probable que hayas escuchado la frase “La optimización prematura es la raíz de todos los males” más de una vez en tu carrera. Si bien la optimización puede no ser muy útil (o absolutamente necesaria) para proyectos pequeños, el perfilado a menudo es útil.
Después de terminar de codificar un módulo, es una buena práctica perfilar tu código para medir cuánto tiempo tarda en ejecutarse cada una de las secciones. Esto puede ayudar a identificar malos olores de código y guiar las optimizaciones para mejorar la calidad del código. ¡Así que siempre perfila tu código antes de optimizarlo!
Para dar los primeros pasos, esta guía te ayudará a comenzar con el perfilado en Python, utilizando los módulos integrados timeit y cProfile. Aprenderás a utilizar tanto la interfaz de línea de comandos como las funciones equivalentes dentro de los scripts de Python.
- Un Enfoque Integral para Mejorar la Seguridad del IoT con Inteligencia Artificial
- Descifrando los desafíos empresariales El arte de crear soluciones analíticas
- Entendiendo Flash-Atención y Flash-Atención-2 El camino para ampliar la longitud del contexto de los modelos de lenguaje
Cómo perfilar código Python utilizando timeit
El módulo timeit es parte de la biblioteca estándar de Python y ofrece algunas funciones de conveniencia que se pueden utilizar para medir el tiempo de ejecución de fragmentos de código cortos.
Tomemos un ejemplo simple de invertir una lista de Python. Mediremos los tiempos de ejecución de obtener una copia invertida de la lista utilizando:
- la función reversed(), y
- el slicing de la lista.
>>> nums=[6,9,2,3,7]
>>> list(reversed(nums))
[7, 3, 2, 9, 6]
>>> nums[::-1]
[7, 3, 2, 9, 6]
Ejecución de timeit en la línea de comandos
Puedes ejecutar timeit en la línea de comandos utilizando la siguiente sintaxis:
$ python -m timeit -s 'código-de-configuración' -n 'número' -r 'repetir' 'stmt'
Debes proporcionar el enunciado stmt cuyo tiempo de ejecución se medirá.
Puedes especificar el código de configuración setup cuando sea necesario, utilizando la opción corta -s o la opción larga –setup. El código de configuración se ejecutará solo una vez.
El número de veces que se ejecutará el enunciado: opción corta -n o opción larga –number es opcional. Y el número de veces que se repetirá este ciclo: opción corta -r o opción larga –repeat también es opcional.
Veamos esto en acción para nuestro ejemplo:
En este caso, la creación de la lista es el código de configuración y invertir la lista es el enunciado que se medirá:
$ python -m timeit -s 'nums=[6,9,2,3,7]' 'list(reversed(nums))'
500000 loops, mejor de 5: 695 nsec por loop
Cuando no especificas valores para repeat, se utiliza el valor predeterminado de 5. Y cuando no especificas number, el código se ejecuta tantas veces como sea necesario para alcanzar un tiempo total de al menos 0.2 segundos.
Este ejemplo establece explícitamente el número de veces que se ejecutará el enunciado:
$ python -m timeit -s 'nums=[6,9,2,3,7]' -n 100Bu000 'list(reversed(nums))'
100000 loops, mejor de 5: 540 nsec por loop
El valor predeterminado de repeat es 5, pero podemos establecerlo en cualquier valor adecuado:
$ python3 -m timeit -s 'nums=[6,9,2,3,7]' -r 3 'list(reversed(nums))'
500000 loops, mejor de 3: 663 nsec por loop
También vamos a medir el enfoque de slicing de la lista:
$ python3 -m timeit -s 'nums=[6,9,2,3,7]' 'nums[::-1]'
1000000 loops, mejor de 5: 142 nsec por loop
El enfoque de división de listas parece ser más rápido (todos los ejemplos están en Python 3.10 en Ubuntu 22.04).
Ejecutando timeit en un script de Python
Aquí está el equivalente de ejecutar timeit dentro del script de Python:
import timeit
setup = 'nums=[9,2,3,7,6]'
number = 100000
stmt1 = 'list(reversed(nums))'
stmt2 = 'nums[::-1]'
t1 = timeit.timeit(setup=setup,stmt=stmt1,number=number)
t2 = timeit.timeit(setup=setup,stmt=stmt2,number=number)
print(f"Utilizando la función reversed(): {t1}")
print(f"Utilizando la división de listas: {t2}")
El callable timeit()
devuelve el tiempo de ejecución de stmt
para number
veces. Observa que podemos mencionar explícitamente el número de veces a ejecutar, o hacer que number
tome el valor predeterminado de 1000000.
Output >>
Utilizando la función reversed(): 0.08982690000000002
Utilizando la división de listas: 0.015550800000000004
Esto ejecuta la declaración, sin repetir la función del temporizador, durante el número especificado de veces y devuelve el tiempo de ejecución. También es bastante común utilizar time.repeat()
y tomar el tiempo mínimo como se muestra a continuación:
import timeit
setup = 'nums=[9,2,3,7,6]'
number = 100000
stmt1 = 'list(reversed(nums))'
stmt2 = 'nums[::-1]'
t1 = min(timeit.repeat(setup=setup,stmt=stmt1,number=number))
t2 = min(timeit.repeat(setup=setup,stmt=stmt2,number=number))
print(f"Utilizando la función reversed(): {t1}")
print(f"Utilizando la división de listas: {t2}")
Esto repetirá el proceso de ejecutar el código el número de veces especificado número de veces y devuelve el tiempo de ejecución mínimo. Aquí tenemos 5 repeticiones de 100000 veces cada una.
Output >>
Utilizando la función reversed(): 0.055375300000000016
Utilizando la división de listas: 0.015101400000000043
Cómo perfilar un script de Python utilizando cProfile
Hemos visto cómo timeit se puede utilizar para medir los tiempos de ejecución de fragmentos de código cortos. Sin embargo, en la práctica, es más útil perfilar un script de Python completo.
Esto nos dará los tiempos de ejecución de todas las funciones y llamadas a métodos, incluyendo funciones y métodos integrados. Por lo tanto, podemos tener una mejor idea de las llamadas a funciones más costosas e identificar oportunidades de optimización. Por ejemplo: puede haber una llamada a una API que es demasiado lenta. O una función puede tener un bucle que puede ser reemplazado por una expresión de comprensión más pythonica.
Aprendamos cómo perfilar scripts de Python utilizando el módulo cProfile (también parte de la biblioteca estándar de Python).
Considera el siguiente script de Python:
# main.py
import time
def func(num):
for i in range(num):
print(i)
def another_func(num):
time.sleep(num)
print(f"Dormido durante {num} segundos")
def useful_func(nums, target):
if target in nums:
return nums.index(target)
if __name__ == "__main__":
func(1000)
another_func(20)
useful_func([2, 8, 12, 4], 12)
Aquí tenemos tres funciones:
func()
que recorre un rango de números y los imprime.another func()
que contiene una llamada a la funciónsleep()
.useful_func()
que devuelve el índice de un número objetivo en la lista (si el objetivo está presente en la lista).
Las funciones mencionadas anteriormente se llamarán cada vez que ejecutes el script main.py.
Ejecutando cProfile en la línea de comandos
Ejecuta cProfile en la línea de comandos usando:
python3 -m nombre-archivo.py
Aquí hemos nombrado el archivo main.py:
python3 -m main.py
Al ejecutar esto, deberías obtener la siguiente salida:
Salida >>
0
...
999
Durmió durante 20 segundos
Y el siguiente perfil:
Aquí, ncalls
se refiere al número de llamadas a la función y percall
se refiere al tiempo por cada llamada a la función. Si el valor de ncalls
es mayor a uno, entonces percall
es el tiempo promedio en todas las llamadas.
El tiempo de ejecución del script está dominado por another_func
que utiliza la llamada a la función integrada sleep
(duerme durante 20 segundos). También vemos que las llamadas a la función print
son bastante costosas.
Usando cProfile en el script de Python
Aunque ejecutar cProfile en la línea de comandos funciona bien, también puedes agregar la funcionalidad de perfilado al script de Python. Puedes usar cProfile junto con el módulo pstats para el perfilado y el acceso a las estadísticas.
Como una buena práctica para manejar mejor la configuración y la liberación de recursos, usa la declaración with y crea un objeto de perfil que se utiliza como un gestor de contexto:
# main.py
import pstats
import time
import cProfile
def func(num):
for i in range(num):
print(i)
def another_func(num):
time.sleep(num)
print(f"Durmio durante {num} segundos")
def useful_func(nums, target):
if target in nums:
return nums.index(target)
if __name__ == "__main__":
with cProfile.Profile() as profile:
func(1000)
another_func(20)
useful_func([2, 8, 12, 4], 12)
profile_result = pstats.Stats(profile)
profile_result.print_stats()
Echemos un vistazo más de cerca al perfil de salida generado:
Cuando estás perfilando un script grande, será útil ordenar los resultados por tiempo de ejecución. Para hacerlo, puedes llamar a sort_stats
en el objeto de perfil y ordenar basándote en el tiempo de ejecución:
...
if __name__ == "__main__":
with cProfile.Profile() as profile:
func(1000)
another_func(20)
useful_func([2, 8, 12, 4], 12)
profile_result = pstats.Stats(profile)
profile_result.sort_stats(pstats.SortKey.TIME)
profile_result.print_stats()
Cuando ahora ejecutes el script, deberías poder ver los resultados ordenados por tiempo:
Conclusión
Espero que esta guía te ayude a comenzar con el perfilado en Python. Recuerda siempre que las optimizaciones nunca deben ser a expensas de la legibilidad. Si estás interesado en aprender sobre otros perfiles, incluyendo paquetes de Python de terceros, echa un vistazo a este artículo sobre perfiles de Python. Bala Priya C es una desarrolladora y escritora técnica de India. Le gusta trabajar en la intersección de las matemáticas, la programación, la ciencia de datos y la creación de contenido. Sus áreas de interés y experiencia incluyen DevOps, ciencia de datos y procesamiento de lenguaje natural. Le gusta leer, escribir, programar y tomar café. Actualmente, está trabajando en aprender y compartir su conocimiento con la comunidad de desarrolladores mediante la creación de tutoriales, guías prácticas, artículos de opinión y más.
We will continue to update Zepes; if you have any questions or suggestions, please contact us!
Was this article helpful?
93 out of 132 found this helpful
Related articles
- 9 Tipos Comunes de Ataques en Sistemas de Inteligencia Artificial
- Generación automática de música utilizando Aprendizaje Profundo
- SafeCoder vs. Asistentes de Código de código cerrado
- Trabajando con Big Data Herramientas y Técnicas
- Investigadores de Sony proponen BigVSAN Revolucionando la calidad de audio con el uso de Slicing Adversarial Networks en vocoders basados en GAN.
- Conoce ResFields Un enfoque novedoso de IA para superar las limitaciones de los campos neurales espaciotemporales en la modelización efectiva de señales temporales largas y complejas.
- Descubriendo los secretos del rendimiento catalítico con Deep Learning Un estudio en profundidad de la Red Neuronal Convolucional ‘Global + Local’ para la detección de alta precisión de catalizadores heterogéneos