Header Ads Widget

Ticker

6/recent/ticker-posts

Cómo probar el rendimiento del código Python: timeit, cProfile y más

 Muchos de los artículos de esta serie aprovechan una función de Python que nos permite probar el rendimiento de nuestro código, y finalmente quería explicar cómo funciona y cómo usarlo.

En este artículo, cubro tres técnicas principales: fuerza bruta timeit, y cProfilePersonalmente, pruebo el rendimiento de mi código con timeitporque lo encuentro fácil de entender, pero las diversas herramientas de generación de perfiles pueden resultarle útiles. Al final, le pediré que demuestre sus nuevas habilidades con un desafío.

Introducción al problema

Para comenzar a hablar sobre cómo probar el rendimiento del código Python, debemos definirlo. En un nivel alto, una prueba de rendimiento es cualquier cosa que verifique la velocidad, confiabilidad, escalabilidad y / o estabilidad del software. Para nuestros propósitos, buscaremos la velocidad. En particular, buscaremos diferentes formas de comparar la velocidad de dos programas usando sus tiempos de ejecución relativos.

Cuando se trata de software de prueba de rendimiento, el proceso no siempre es fácil u obvio. En particular, hay muchas trampas. Por ejemplo, las pruebas de rendimiento pueden verse influenciadas por todo tipo de factores, como procesos en segundo plano (es decir, Spotify, Eclipse, GitHub Desktop, etc.).

Además, las pruebas de rendimiento no siempre se escriben de una manera que justifique las diferencias en la implementación. Por ejemplo, podría tener dos fragmentos de código que tienen el mismo comportamiento, excepto que uno requiere que se importe una biblioteca. Cuando ejecuto mi prueba, no quiero que la importación afecte el resultado de la prueba. Como resultado, debería escribir mis pruebas de modo que no empiece a medir el tiempo hasta que se importe la biblioteca.

Además de todo eso, es importante tener en cuenta diferentes tipos de escenarios al realizar pruebas de rendimiento. Por ejemplo, si tenemos dos fragmentos similares, uno podría tener un mejor rendimiento para conjuntos de datos más grandes. Es importante probar una variedad de conjuntos de datos por ese motivo.

En cualquier caso, el objetivo de este artículo es analizar algunas formas diferentes en las que podemos probar el rendimiento del código en Python. ¡Vamos a profundizar en!

Soluciones

Como siempre, me gusta compartir algunas formas de lograr nuestra tarea. Por supuesto, si ha estado siguiendo esta serie, sabrá que prefiero usar la timeitbiblioteca para probar fragmentos. Afortunadamente, hay más opciones si timeitno es para ti.

Pruebas de rendimiento por fuerza bruta

Si nunca antes ha realizado ninguna prueba de rendimiento, probablemente ya sepa cómo empezar. Por lo general, queremos tomar una marca de tiempo antes y después de ejecutar nuestro fragmento de código. Luego, podemos calcular la diferencia entre esos tiempos y usar el resultado en nuestra comparación con otros fragmentos.

Para hacer esto en Python, podemos aprovechar la datetimebiblioteca:

1
2
3
4
5
import datetime
start_time = datetime.datetime.now()
# insert code snippet here
end_time = datetime.datetime.now()
print end_time - start_time

Por supuesto, esta solución deja mucho que desear. Por ejemplo, solo nos da un único punto de datos. Idealmente, querríamos ejecutar esto varias veces para recopilar un promedio o al menos un límite inferior, pero esto puede funcionar en un apuro.

Pruebas de rendimiento con la timeitbiblioteca

Si prefiere que toda esta basura de marca de tiempo se extraiga con la adición de algunas ventajas, consulte la timeitbiblioteca. Con la timeitbiblioteca, hay básicamente dos formas principales de probar el código: línea de comandos o en línea. Para nuestros propósitos, echaremos un vistazo a la versión en línea, ya que eso es lo que uso para todas mis pruebas.

Para probar el código utilizando la timeitbiblioteca, deberá llamar a la timeitfunción o la repeatfunción. Cualquiera de los dos está bien, pero la repeatfunción da un poco más de control.

Como ejemplo, probaremos el siguiente fragmento de código de un artículo anterior sobre listas por comprensión :

1
[(a, b) for a in (1, 3, 5) for b in (2, 4, 6)]

En este fragmento, estamos generando una lista de pares a partir de dos tuplas. Para probarlo, podríamos usar la timeitfunción:

1
2
import timeit
timeit.timeit("[(a, b) for a in (1, 3, 5) for b in (2, 4, 6)]")

Si se hace correctamente, ejecutará el fragmento un millón de veces y, como resultado, devolverá un tiempo de ejecución promedio. Por supuesto, puede cambiar el número de iteraciones utilizando el numberargumento de palabra clave:

1
2
import timeit
timeit.timeit("[(a, b) for a in (1, 3, 5) for b in (2, 4, 6)]", number=1000)

Naturalmente, podemos llevar esta prueba un paso más allá ejecutándola varias veces utilizando la función de repetición:

1
2
import timeit
timeit.repeat("[(a, b) for a in (1, 3, 5) for b in (2, 4, 6)]")

En lugar de devolver un tiempo de ejecución, esta función devuelve una lista de tiempos de ejecución. En este caso, la lista contendrá tres tiempos de ejecución separados. Por supuesto, no necesitamos todos esos momentos. En su lugar, podemos devolver el tiempo de ejecución más pequeño, por lo que podemos tener una idea del límite inferior del fragmento:

1
2
import timeit
min(timeit.repeat("[(a, b) for a in (1, 3, 5) for b in (2, 4, 6)]"))

Si ha estado un poco, probablemente haya visto esta sintaxis exacta en mis pruebas de rendimiento en otros artículos o videos. Por supuesto, hago un esfuerzo adicional y aumento el número de repeticiones, pero probablemente sea una exageración. En cualquier caso, esta es una excelente manera de probar el rendimiento de los fragmentos de código de Python.

Pruebas de rendimiento con la biblioteca cProfile

Fuera de timeitla fuerza bruta absoluta, siempre puede aprovechar otras herramientas de creación de perfiles como cProfile . Al igual timeit, podemos aprovechar cProfilepara obtener estadísticas de tiempo de ejecución de una sección de código. Por supuesto, cProfile es un poco más detallado. Por ejemplo, podemos ejecutar la misma lista de comprensión de arriba de la siguiente manera:

1
2
import cProfile
cProfile.run("[(a, b) for a in (1, 3, 5) for b in (2, 4, 6)]")

Como resultado, obtienes un buen informe que se ve así:

1
2
3
4
5
6
7
8
9
4 function calls in 0.000 seconds
 
   Ordered by: standard name
 
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.000    0.000 <string>:1(<listcomp>)
        1    0.000    0.000    0.000    0.000 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {built-in method builtins.exec}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

Aquí, obtenemos una buena tabla que incluye mucha información útil. Específicamente, cada fila indica una función que se ejecutó y cada columna desglosa un segmento de tiempo de ejecución diferente. Por ejemplo, la <listcomp>función se llamó una vez ( ncalls) y tomó 0.000 segundos ( tottime) excluyendo las llamadas a las subfunciones. Para comprender todo lo demás en esta tabla, consulte el siguiente desglose de las seis columnas:

  • ncalls : la cantidad de veces que se llamó a esa función en particular
    • En realidad, este número puede escribirse como una fracción (p 3/1Ej. ) Donde el primer valor es el número de llamadas totales y el segundo valor es el número de llamadas primitivas (no recursivas).
  • tottime : la cantidad total de tiempo que la función pasó ejecutándose sin incluir llamadas a subfunciones
  • percall (primera): la proporción de tottime a ncalls (es decir, la cantidad promedio de tiempo dedicado a esta función excluyendo subfunciones)
  • cumtime : la cantidad total de tiempo que la función pasó ejecutándose, incluidas las llamadas a subfunciones
  • percall (segundo): la relación entre el tiempo acumulado y las llamadas primitivas (es decir, la cantidad promedio de tiempo empleado en esta función)
  • nombre de archivo: lineno (función) : el nombre de archivo, número de línea y función en cuestión

Como puede ver, le cProfileayuda a echar un vistazo al funcionamiento interno de un fragmento de código. Por supuesto, no obtienes tiempos precisos, por lo que esto funciona mejor como un complemento en timeitlugar de un reemplazo. Dicho esto, creo cProfileque sería excelente para crear perfiles de scripts grandes. De esa forma, puede determinar qué funciones necesitan optimización.

Prueba de rendimiento con bibliotecas externas

Si bien Python proporciona muchas formas de comparar su propio código, también hay otras bibliotecas que podemos aprovechar. Por ejemplo:

Personalmente, nunca he usado ninguna de estas herramientas, pero sentí que debería compartirlas en aras de la integridad. No dude en seguir esos enlaces para obtener más información.

Desafío

En este punto, normalmente compartiría algunas métricas de rendimiento para cada una de las soluciones anteriores, pero eso realmente no tiene sentido en este contexto. En cambio, ¡es hora de saltar directamente al desafío!

Elija uno de los artículos de esta serie y ejecute sus propias métricas de rendimiento en cada una de las soluciones. Dado que normalmente corro timeit, tal vez podrías intentar usar alguna de las otras herramientas de este artículo. Por ejemplo, intente ejecutar cProfile en todas las soluciones de formato de cadenas .

Cuando haya terminado, comparta los mejores resultados en los comentarios. ¡Me interesa ver lo que aprendes! Mientras lo hace, revise mi trabajo. Me encantaría saber si me faltan otras soluciones.

Un pequeño resumen

Como siempre, me gusta terminar las cosas con una lista de opciones. Tenga en cuenta que cada solución aprovecha un fragmento de código de ejemplo. En este caso, elegí una lista de comprensión, pero puedes usar cualquier fragmento:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
# Brute force solution
import datetime
start_time = datetime.datetime.now()
[(a, b) for a in (1, 3, 5) for b in (2, 4, 6)] # example snippet
end_time = datetime.datetime.now()
print end_time - start_time
 
# timeit solution
import timeit
min(timeit.repeat("[(a, b) for a in (1, 3, 5) for b in (2, 4, 6)]"))
 
# cProfile solution
import cProfile
cProfile.run("[(a, b) for a in (1, 3, 5) for b in (2, 4, 6)]")

Bueno, eso es todo lo que tengo! Si tiene alguna herramienta de rendimiento propia para agregar a la lista, no dude en compartirla en los comentarios.

Publicar un comentario

0 Comentarios