Cómo realizar benchmarks en código Python con pytest-benchmark

Everett Pompeii

Everett Pompeii


¿Qué es la Evaluación Comparativa?

La evaluación comparativa es la práctica de probar el rendimiento de tu código para ver qué tan rápido (latencia) o cuánto (rendimiento) trabajo puede hacer. Este paso, a menudo pasado por alto en el desarrollo de software, es crucial para crear y mantener un código rápido y de alto rendimiento. La evaluación comparativa proporciona las métricas necesarias para que los desarrolladores comprendan cómo se comporta su código bajo diversas cargas de trabajo y condiciones. Por las mismas razones por las cuales escribes pruebas unitarias y de integración para prevenir regresiones de características, debes escribir evaluaciones comparativas para prevenir regresiones de rendimiento. ¡Los errores de rendimiento son errores!

Escribe FizzBuzz en Python

Para escribir evaluaciones comparativas, necesitamos algún código fuente para comparar. Para empezar, vamos a escribir un programa muy simple, FizzBuzz.

Las reglas para FizzBuzz son las siguientes:

Escribe un programa que imprima los números enteros del 1 al 100 (incluyendo ambos):

  • Para múltiplos de tres, imprime Fizz
  • Para múltiplos de cinco, imprime Buzz
  • Para múltiplos de ambos, tres y cinco, imprime FizzBuzz
  • Para todos los demás, imprime el número

Hay muchas formas de escribir FizzBuzz. Así que vamos a elegir mi favorita:

for i in range(1, 101):
if n % 15 == 0:
print("FizzBuzz")
elif n % 3 == 0:
print("Fizz")
elif n % 5 == 0:
print("Buzz")
else:
print(i)
  • Itera desde 1 hasta 100, utilizando un rango de 101.
  • Para cada número, calcula el módulo (resto después de la división) tanto para 3 como para 5.
  • Si el resto es 0, entonces el número es un múltiplo del factor dado.
    • Si el resto es 0 para 15, entonces imprime FizzBuzz.
    • Si el resto es 0 para 3, entonces imprime Fizz.
    • Si el resto es 0 para 5, entonces imprime Buzz.
  • De lo contrario, simplemente imprime el número.

Sigue el Paso a Paso

Para seguir este tutorial paso a paso, necesitarás instalar Python e instalar pipenv.

🐰 El código fuente de este post está disponible en GitHub.

Crea un archivo de Python llamado game.py, y establece su contenido con la implementación de FizzBuzz anterior.

Luego ejecuta python game.py. La salida debería verse como:

$ python game.py
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
...
97
98
Fizz
Buzz

🐰 ¡Boom! ¡Estás rompiendo la entrevista de codificación!

Antes de continuar, es importante discutir las diferencias entre micro-benchmarking y macro-benchmarking.

Micro-Benchmarking vs Macro-Benchmarking

Existen dos categorías principales de benchmarks de software: micro-benchmarks y macro-benchmarks. Los micro-benchmarks operan a un nivel similar a las pruebas unitarias. Por ejemplo, un benchmark para una función que determina Fizz, Buzz, o FizzBuzz para un único número sería un micro-benchmark. Los macro-benchmarks operan a un nivel similar a las pruebas de integración. Por ejemplo, un benchmark para una función que juega el juego completo de FizzBuzz, desde 1 hasta 100, sería un macro-benchmark.

Generalmente, es mejor probar al nivel más bajo de abstracción posible. En el caso de los benchmarks, esto los hace más fáciles de mantener, y ayuda a reducir la cantidad de ruido en las mediciones. Sin embargo, al igual que tener algunas pruebas de extremo a extremo puede ser muy útil para verificar la cordura todo el sistema se junta como se esperaba, tener macro-benchmarks puede ser muy útil para asegurarse de que los caminos críticos a través de su software se mantienen con buen rendimiento.

Benchmarking en Python

Las dos opciones populares para benchmarking en Python son: pytest-benchmark y airspeed velocity (asv)

pytest-benchmark es una herramienta de benchmarking poderosa integrada con el popular marco de pruebas pytest. Permite a los desarrolladores medir y comparar el rendimiento de su código ejecutando benchmarks junto a sus pruebas unitarias. Los usuarios pueden comparar fácilmente sus resultados de benchmark localmente y exportar sus resultados en varios formatos, como JSON.

airspeed velocity (asv) es otra herramienta de benchmarking avanzada en el ecosistema de Python. Uno de los beneficios clave de asv es su capacidad para generar informes HTML detallados e interactivos, lo que facilita la visualización de tendencias de rendimiento e identificación de regresiones. Además, asv soporta Benchmarking Continuo Relativo de forma nativa.

Ambos son soportados por Bencher. Entonces, ¿por qué elegir pytest-benchmark? pytest-benchmark se integra perfectamente con pytest, que es el estándar de facto para pruebas unitarias en el ecosistema de Python. Recomendaría usar pytest-benchmark para hacer benchmarking de la latencia de tu código, especialmente si ya estás usando pytest. Es decir, pytest-benchmark es ideal para medir el tiempo de reloj de pared.

Refactorizar FizzBuzz

Para probar nuestra aplicación FizzBuzz, necesitamos desacoplar nuestra lógica de la ejecución principal de nuestro programa. Los bancos de pruebas no pueden evaluar la ejecución principal. Para hacer esto, necesitamos realizar algunos cambios.

Vamos a refactorizar nuestra lógica FizzBuzz en un par de funciones:

def play_game(n, should_print):
result = fizz_buzz(n)
if should_print:
print(result)
return result
def fizz_buzz(n):
if n % 15 == 0:
return "FizzBuzz"
elif n % 3 == 0:
return "Fizz"
elif n % 5 == 0:
return "Buzz"
else:
return str(n)
  • play_game: Recibe un número entero n, llama a fizz_buzz con ese número, y si should_print es True, imprime el resultado.
  • fizz_buzz: Recibe un número entero n y realiza la lógica real de Fizz, Buzz, FizzBuzz o número, devolviendo el resultado como una cadena.

Luego, actualiza la ejecución principal para que se vea así:

game.py
for i in range(1, 101):
play_game(i, True)

La ejecución principal de nuestro programa itera a través de los números del 1 al 100 inclusive y llama a play_game para cada número, con should_print configurado en True.

Evaluando el Rendimiento de FizzBuzz

Para evaluar el rendimiento de nuestro código, necesitamos crear una función de prueba que ejecute nuestra evaluación. Al final de game.py añade el siguiente código:

def test_game(benchmark):
def run_game():
for i in range(1, 101):
play_game(i, False)
benchmark(run_game)
  • Crea una función llamada test_game que acepte un benchmark de pytest-benchmark como fixture.
  • Crea una función run_game que itere de 1 a 100 inclusive.
    • Para cada número, llama a play_game, con should_print configurado en False.
  • Pasa la función run_game al ejecutor benchmark.

Ahora necesitamos configurar nuestro proyecto para ejecutar nuestras evaluaciones.

Crea un nuevo entorno virtual con pipenv:

$ pipenv shell
Creating a Pipfile for this project...
Launching subshell in virtual environment...
source /usr/bencher/.local/share/virtualenvs/test-xnizGmtA/bin/activate

Instala pytest-benchmark dentro de ese nuevo entorno pipenv:

$ pipenv install pytest-benchmark
Creating a Pipfile for this project...
Installing pytest-benchmark...
Resolving pytest-benchmark...
Added pytest-benchmark to Pipfile's [packages] ...
✔ Installation Succeeded
Pipfile.lock not found, creating...
Locking [packages] dependencies...
Building requirements...
Resolving dependencies...
✔ Success!
Locking [dev-packages] dependencies...
Updated Pipfile.lock (be953321071292b6175f231c7e2e835a3cd26169a0d52b7b781b344d65e8cce3)!
Installing dependencies from Pipfile.lock (e8cce3)...

Ahora estamos listos para medir el rendimiento de nuestro código, ejecuta pytest game.py:

$ pytest game.py
======================================================= test session starts ========================================================
platform darwin -- Python 3.12.7, pytest-8.3.3, pluggy-1.5.0
benchmark: 4.0.0 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /usr/bencher/examples/python/pytest_benchmark
plugins: benchmark-4.0.0
collected 1 item
game.py . [100%]
------------------------------------------------- benchmark: 1 tests -------------------------------------------------
Name (time in us) Min Max Mean StdDev Median IQR Outliers OPS (Kops/s) Rounds Iterations
----------------------------------------------------------------------------------------------------------------------
test_game 10.5416 237.7499 10.8307 1.3958 10.7088 0.1248 191;10096 92.3304 57280 1
----------------------------------------------------------------------------------------------------------------------
Legend:
Outliers: 1 Standard Deviation from Mean; 1.5 IQR (InterQuartile Range) from 1st Quartile and 3rd Quartile.
OPS: Operations Per Second, computed as 1 / Mean
======================================================== 1 passed in 1.68s =========================================================

🐰 ¡A zapatear se ha dicho! ¡Tenemos nuestras primeras métricas de evaluación!

Finalmente, podemos descansar nuestras cansadas cabezas de desarrollador… ¡Es broma, nuestros usuarios quieren una nueva característica!

Escribe FizzBuzzFibonacci en Python

Nuestros Indicadores Clave de Rendimiento (KPIs) están bajos, por lo que nuestro Gerente de Producto (PM) quiere que añadamos una nueva funcionalidad. Después de mucho brainstorming y muchas entrevistas con usuarios, se decide que el buen FizzBuzz no es suficiente. Los niños de hoy quieren un nuevo juego, FizzBuzzFibonacci.

Las reglas para FizzBuzzFibonacci son las siguientes:

Escribe un programa que imprima los enteros del 1 al 100 (inclusive):

  • Para múltiplos de tres, imprime Fizz
  • Para múltiplos de cinco, imprime Buzz
  • Para múltiplos de tres y cinco, imprime FizzBuzz
  • Para números que sean parte de la secuencia de Fibonacci, solo imprime Fibonacci
  • Para todos los demás, imprime el número

La secuencia de Fibonacci es una serie en la que cada número es la suma de los dos números anteriores. Por ejemplo, comenzando con 0 y 1 el siguiente número en la secuencia de Fibonacci sería 1. Seguido de: 2, 3, 5, 8 y así sucesivamente. Los números que forman parte de la secuencia de Fibonacci se conocen como números de Fibonacci. Por lo tanto, tendremos que escribir una función que detecte los números de Fibonacci.

Hay muchas formas de escribir la secuencia de Fibonacci y de igual forma muchas maneras de detectar un número de Fibonacci. Así que elegiremos mi forma favorita:

def is_fibonacci_number(n):
for i in range(n + 1):
previous, current = 0, 1
while current < i:
next_value = previous + current
previous = current
current = next_value
if current == n:
return True
return False
  • Crea una función llamada is_fibonacci_number que reciba un entero y devuelva un booleano.
  • Itera para todos los números desde 0 hasta nuestro número dado n inclusive.
  • Inicializa nuestra secuencia de Fibonacci comenzando con 0 y 1 como los números anterior y actual respectivamente.
  • Itera mientras el número actual sea menor que la iteración actual i.
  • Suma los números anterior y actual para obtener el número next_value.
  • Actualiza el número anterior al número actual.
  • Actualiza el número actual al número next_value.
  • Una vez que actual sea mayor o igual al número dado n, saldremos del bucle.
  • Verifica si el número actual es igual al número dado n y si es así, devuelve True.
  • De lo contrario, devuelve False.

Ahora necesitaremos actualizar nuestra función fizz_buzz:

def fizz_buzz_fibonacci(n):
if is_fibonacci_number(n):
return "Fibonacci"
elif n % 15 == 0:
return "FizzBuzz"
elif n % 3 == 0:
return "Fizz"
elif n % 5 == 0:
return "Buzz"
else:
return str(n)
  • Renombra la función fizz_buzz a fizz_buzz_fibonacci para que sea más descriptiva.
  • Llama a nuestra función auxiliar is_fibonacci_number.
  • Si el resultado de is_fibonacci_number es True entonces devuelve Fibonacci.
  • Si el resultado de is_fibonacci_number es False entonces realiza la misma lógica de Fizz, Buzz, FizzBuzz, o número devolviendo el resultado.

Debido a que renombramos fizz_buzz a fizz_buzz_fibonacci, también necesitamos actualizar nuestra función play_game:

def play_game(n, should_print):
result = fizz_buzz_fibonacci(n)
if should_print:
print(result)
return result

Tanto nuestra ejecución principal como la función test_game pueden permanecer exactamente iguales.

Evaluación comparativa de FizzBuzzFibonacci

Ahora podemos volver a ejecutar nuestro benchmark:

$ pytest game.py
======================================================= test session starts ========================================================
platform darwin -- Python 3.12.7, pytest-8.3.3, pluggy-1.5.0
benchmark: 4.0.0 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /usr/bencher/examples/python/pytest_benchmark
plugins: benchmark-4.0.0
collected 1 item
game.py . [100%]
--------------------------------------------------- benchmark: 1 tests --------------------------------------------------
Name (time in us) Min Max Mean StdDev Median IQR Outliers OPS (Kops/s) Rounds Iterations
-------------------------------------------------------------------------------------------------------------------------
test_game 726.9592 848.2919 735.5682 13.4925 731.4999 4.7078 146;192 1.3595 1299 1
-------------------------------------------------------------------------------------------------------------------------
Legend:
Outliers: 1 Standard Deviation from Mean; 1.5 IQR (InterQuartile Range) from 1st Quartile and 3rd Quartile.
OPS: Operations Per Second, computed as 1 / Mean
======================================================== 1 passed in 1.97s =========================================================

Revisando nuestro historial de terminal, podemos hacer una comparación visual entre el rendimiento de nuestros juegos FizzBuzz y FizzBuzzFibonacci: 10.8307 us vs 735.5682 us. Tus números serán un poco diferentes a los míos. Sin embargo, la diferencia entre los dos juegos probablemente esté en el rango de 50x. ¡Eso me parece bien! Especialmente al agregar una característica que suena tan elegante como Fibonacci a nuestro juego. ¡A los niños les encantará!

Expandir FizzBuzzFibonacci en Python

¡Nuestro juego es un éxito! De verdad, a los niños les encanta jugar FizzBuzzFibonacci. ¡Tanto que ha llegado la noticia de los ejecutivos de que quieren una secuela! Pero este es el mundo moderno, ¡necesitamos ingresos recurrentes anuales (ARR) y no compras únicas! La nueva visión para nuestro juego es que sea abierto, ya no vivir entre los límites de 1 y 100 (incluso si es inclusivo). ¡No, vamos hacia nuevas fronteras!

Las reglas para Open World FizzBuzzFibonacci son las siguientes:

Escribe un programa que tome cualquier número entero positivo e imprima:

  • Para múltiplos de tres, imprime Fizz
  • Para múltiplos de cinco, imprime Buzz
  • Para múltiplos de tres y cinco, imprime FizzBuzz
  • Para números que son parte de la secuencia Fibonacci, sólo imprime Fibonacci
  • Para todos los demás, imprime el número

Para que nuestro juego funcione con cualquier número, necesitaremos aceptar un argumento de línea de comandos. Actualiza la ejecución principal para que se vea así:

game.py
import sys
args = sys.argv
if len(args) > 1 and args[1].isdigit():
i = int(args[1])
play_game(i, True)
else:
print("Please, enter a positive integer to play...")
  • Importar el paquete sys.
  • Recoger todos los argumentos (args) pasados a nuestro juego desde la línea de comandos.
  • Obtener el primer argumento pasado a nuestro juego y verificar si es un dígito.
    • Si es así, analiza el primer argumento como un entero, i.
    • Juega nuestro juego con el nuevo entero analizado i.
  • Si el análisis falla o no se pasa ningún argumento, por defecto solicitar una entrada válida.

¡Ahora podemos jugar a nuestro juego con cualquier número! Ejecuta python game.py seguido de un número entero para jugar a nuestro juego:

$ python game.py 9
Fizz
$ python game.py 10
Buzz
$ python game.py 13
Fibonacci

Y si omitimos o proporcionamos un número no válido:

$ python game.py
Please, enter a positive integer to play...
$ python game.py bad
Please, enter a positive integer to play...

¡Vaya, eso fue una prueba exhaustiva! La integración continua pasa. Nuestros jefes están emocionados. ¡Enviémoslo! 🚀

El Fin


SpongeBob SquarePants Tres Semanas Después
Meme de Esto está Bien

🐰 … ¿el fin de tu carrera tal vez?


¡Solo bromeaba! ¡Todo está en llamas! 🔥

Bueno, al principio todo parecía ir bien. Y luego a las 02:07 AM del sábado, mi buscapersonas sonó:

📟 ¡Tu juego está en llamas! 🔥

Después de salir de la cama a la carrera, traté de averiguar qué estaba pasando. Intenté buscar en los registros, pero eso fue difícil porque todo seguía fallando. Finalmente, encontré el problema. ¡Los niños! Les encantaba tanto nuestro juego, que lo estaban jugando hasta llegar al millón! En un destello de genialidad, agregué dos nuevos benchmarks:

def test_game_100(benchmark):
def run_game():
play_game(100, False)
benchmark(run_game)
def test_game_1_000_000(benchmark):
def run_game():
play_game(1_000_000, False)
benchmark(run_game)
  • Un micro-benchmark test_game_100 para jugar el juego con el número cien (100)
  • Un micro-benchmark test_game_1_000_000 para jugar el juego con el número un millón (1_000_000)

Cuando lo ejecuté, obtuve esto:

$ pytest game.py
======================================================= test session starts ========================================================
platform darwin -- Python 3.12.7, pytest-8.3.3, pluggy-1.5.0
benchmark: 4.0.0 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /usr/bencher/examples/python/pytest_benchmark
plugins: benchmark-4.0.0
collected 3 items
game.py ... [100%]

Espera… espera…

-------------------------------------------------------------------------------------------------- benchmark: 3 tests --------------------------------------------------------------------------------------------------
Name (time in us) Min Max Mean StdDev Median IQR Outliers OPS Rounds Iterations
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test_game_100 15.4166 (1.0) 112.8749 (1.0) 15.8470 (1.0) 1.1725 (1.0) 15.6672 (1.0) 0.1672 (1.0) 1276;7201 63,103.3078 (1.0) 58970 1
test_game 727.0002 (47.16) 1,074.3327 (9.52) 754.3231 (47.60) 33.2047 (28.32) 748.9999 (47.81) 33.7283 (201.76) 134;54 1,325.6918 (0.02) 1319 1
test_game_1_000_000 565,232.3328 (>1000.0) 579,829.1252 (>1000.0) 571,684.6334 (>1000.0) 6,365.1577 (>1000.0) 568,294.3747 (>1000.0) 10,454.0113 (>1000.0) 2;0 1.7492 (0.00) 5 1
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Legend:
Outliers: 1 Standard Deviation from Mean; 1.5 IQR (InterQuartile Range) from 1st Quartile and 3rd Quartile.
OPS: Operations Per Second, computed as 1 / Mean
======================================================== 3 passed in 7.01s =========================================================

¿Qué! 15.8470 us x 1,000 debería ser 15,847.0 us no 571,684.6334 us 🤯 Aunque obtuve mi código de la secuencia de Fibonacci funcionalmente correcto, debo tener un error de rendimiento en algún lugar.

Corregir FizzBuzzFibonacci en Python

Echemos un vistazo de nuevo a esa función is_fibonacci_number:

def is_fibonacci_number(n):
for i in range(n + 1):
previous, current = 0, 1
while current < i:
next_value = previous + current
previous = current
current = next_value
if current == n:
return True
return False

Ahora que estoy pensando en el rendimiento, me doy cuenta de que tengo un bucle adicional innecesario. Podemos deshacernos completamente del bucle for i in range(n + 1): y simplemente comparar el valor current con el número dado (n) 🤦

def is_fibonacci_number(n):
previous, current = 0, 1
while current < n:
next_value = previous + current
previous = current
current = next_value
return current == n
  • Actualiza nuestra función is_fibonacci_number.
  • Inicia nuestra secuencia de Fibonacci comenzando con 0 y 1 como los números previous y current respectivamente.
  • Itera mientras el número current sea menor que el número dado n.
  • Suma los números previous y current para obtener el número next_value.
  • Actualiza el número previous al número current.
  • Actualiza el número current al número next_value.
  • Una vez que current sea mayor o igual al número dado n, saldremos del bucle.
  • Verifica si el número current es igual al número dado n y devuelve ese resultado.

Ahora vamos a ejecutar nuevamente esos benchmarks y ver cómo lo hicimos:

$ pytest game.py
======================================================= test session starts ========================================================
platform darwin -- Python 3.12.7, pytest-8.3.3, pluggy-1.5.0
benchmark: 4.0.0 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /usr/bencher/examples/python/pytest_benchmark
plugins: benchmark-4.0.0
collected 3 items
game.py ... [100%]
------------------------------------------------------------------------------------------------ benchmark: 3 tests ------------------------------------------------------------------------------------------------
Name (time in ns) Min Max Mean StdDev Median IQR Outliers OPS (Kops/s) Rounds Iterations
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test_game_100 309.8685 (1.0) 40,197.8614 (2.38) 322.0815 (1.0) 101.7570 (1.0) 320.2877 (1.0) 5.1805 (1.0) 321;12616 3,104.8046 (1.0) 195120 16
test_game_1_000_000 724.9881 (2.34) 16,912.4920 (1.0) 753.1445 (2.34) 121.0458 (1.19) 741.7053 (2.32) 12.4797 (2.41) 656;13698 1,327.7664 (0.43) 123073 10
test_game 26,958.9946 (87.00) 129,667.1107 (7.67) 27,448.7719 (85.22) 1,555.0003 (15.28) 27,291.9424 (85.21) 165.7754 (32.00) 479;2372 36.4315 (0.01) 25918 1
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Legend:
Outliers: 1 Standard Deviation from Mean; 1.5 IQR (InterQuartile Range) from 1st Quartile and 3rd Quartile.
OPS: Operations Per Second, computed as 1 / Mean
======================================================== 3 passed in 3.99s =========================================================

¡Oh, wow! Nuestro benchmark test_game ha vuelto a estar cerca de donde estaba para el FizzBuzz original. Desearía poder recordar exactamente cuál era ese puntaje. Pero han pasado tres semanas. Mi historial de terminal no retrocede tanto. Y pytest-benchmark solo almacena sus resultados cuando se lo pedimos. ¡Pero creo que está cerca!

El benchmark test_game_100 ha bajado casi 50x a 322.0815 ns. ¡Y el benchmark test_game_1_000_000 ha bajado más de 500,000x! 571,684,633.4 ns a 753.1445 ns!

🐰 Al menos atrapamos este error de rendimiento antes de que llegara a producción… oh, cierto. Olvídalo…

Detectar Retrocesos de Rendimiento en CI

Los ejecutivos no estaban contentos con la avalancha de críticas negativas que recibió nuestro juego debido a mi pequeño error de rendimiento. Me dijeron que no dejara que volviera a ocurrir, y cuando les pregunté cómo, simplemente me dijeron que no volviera a hacerlo. ¡¿Cómo se supone que debería manejar eso‽

Afortunadamente, he encontrado esta increíble herramienta de código abierto llamada Bencher. Hay un nivel gratuito súper generoso, así que puedo usar Bencher Cloud para mis proyectos personales. Y en el trabajo, donde todo debe estar en nuestra nube privada, he comenzado a usar Bencher Self-Hosted.

Bencher tiene adaptadores incorporados, por lo que es fácil de integrar en CI. Después de seguir la guía de inicio rápido, ya puedo ejecutar mis referencias y seguir su progreso con Bencher.

$ bencher run --adapter python_pytest --file results.json "pytest --benchmark-json results.json game.py"
======================================================= test session starts ========================================================
platform darwin -- Python 3.12.7, pytest-8.3.3, pluggy-1.5.0
benchmark: 4.0.0 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /usr/bencher/examples/python/pytest_benchmark
plugins: benchmark-4.0.0
collected 3 items
game.py ...
...
View results:
- test_game (Latency): https://bencher.dev/console/projects/game/perf?measures=52507e04-ffd9-4021-b141-7d4b9f1e9194&branches=3a27b3ce-225c-4076-af7c-75adbc34ef9a&testbeds=bc05ed88-74c1-430d-b96a-5394fdd18bb0&benchmarks=077449e5-5b45-4c00-bdfb-3a277413180d&start_time=1697224006000&end_time=1699816009000&upper_boundary=true
- test_game_100 (Latency): https://bencher.dev/console/projects/game/perf?measures=52507e04-ffd9-4021-b141-7d4b9f1e9194&branches=3a27b3ce-225c-4076-af7c-75adbc34ef9a&testbeds=bc05ed88-74c1-430d-b96a-5394fdd18bb0&benchmarks=96508869-4fa2-44ac-8e60-b635b83a17b7&start_time=1697224006000&end_time=1699816009000&upper_boundary=true
- test_game_1_000_000 (Latency): https://bencher.dev/console/projects/game/perf?measures=52507e04-ffd9-4021-b141-7d4b9f1e9194&branches=3a27b3ce-225c-4076-af7c-75adbc34ef9a&testbeds=bc05ed88-74c1-430d-b96a-5394fdd18bb0&benchmarks=ff014217-4570-42ea-8813-6ed0284500a4&start_time=1697224006000&end_time=1699816009000&upper_boundary=true

Usando este práctico dispositivo de viaje en el tiempo que un amable conejo me dio, pude retroceder en el tiempo y reproducir lo que habría sucedido si hubiéramos estado utilizando Bencher todo el tiempo. Puedes ver dónde publicamos por primera vez la implementación defectuosa de FizzBuzzFibonacci. Inmediatamente recibí fallas en CI como un comentario en mi solicitud de extracción. Ese mismo día, arreglé el error de rendimiento, eliminando ese bucle extra innecesario. No hubo incendios. Solo usuarios contentos.

Bencher: Benchmarking continuo

🐰 Bencher

Bencher es un conjunto de herramientas de benchmarking continuo. ¿Alguna vez has tenido un impacto de regresión de rendimiento en tus usuarios? Bencher podría haber evitado que eso sucediera. Bencher te permite detectar y prevenir las regresiones de rendimiento antes de que lleguen a producción.

  • Ejecutar: Ejecute sus benchmarks localmente o en CI usando sus herramientas de benchmarking favoritas. La CLI bencher simplemente envuelve su arnés de benchmarks existente y almacena sus resultados.
  • Seguir: Sigue los resultados de tus benchmarks con el tiempo. Monitoriza, realiza consultas y representa gráficamente los resultados utilizando la consola web de Bencher basándose en la rama de origen, el banco de pruebas y la medida.
  • Capturar: Captura las regresiones de rendimiento en CI. Bencher utiliza analíticas de vanguardia y personalizables para detectar regresiones de rendimiento antes de que lleguen a producción.

Por las mismas razones que las pruebas unitarias se ejecutan en CI para prevenir regresiones funcionales, los benchmarks deberían ejecutarse en CI con Bencher para prevenir regresiones de rendimiento. ¡Los errores de rendimiento son errores!

Empiece a capturar regresiones de rendimiento en CI — prueba Bencher Cloud gratis.

🤖 Este documento fue generado automáticamente por OpenAI GPT-4. Puede que no sea exacto y contenga errores. Si encuentra algún error, abra un problema en GitHub.