Comment évaluer les performances du code Python avec pytest-benchmark

Everett Pompeii

Everett Pompeii


Qu’est-ce que le Benchmarking ?

Le benchmarking est la pratique consistant à tester les performances de votre code pour voir à quelle vitesse (latence) ou combien (débit) de travail il peut effectuer. Cette étape souvent négligée dans le développement logiciel est cruciale pour créer et maintenir un code rapide et performant. Le benchmarking fournit les métriques nécessaires aux développeurs pour comprendre comment leur code se comporte sous diverses charges de travail et conditions. Pour les mêmes raisons que vous écrivez des tests unitaires et d’intégration pour éviter les régressions de fonctionnalités, vous devriez écrire des benchmarks pour éviter les régressions de performances. Les bugs de performance sont des bugs !

Écrire FizzBuzz en Python

Pour écrire des benchmarks, nous avons besoin de code source à évaluer. Pour commencer, nous allons écrire un programme très simple, FizzBuzz.

Les règles pour FizzBuzz sont les suivantes :

Écrivez un programme qui imprime les entiers de 1 à 100 (inclus) :

  • Pour les multiples de trois, imprimez Fizz
  • Pour les multiples de cinq, imprimez Buzz
  • Pour les multiples de trois et de cinq, imprimez FizzBuzz
  • Pour tous les autres, imprimez le numéro

Il existe de nombreuses façons d’écrire FizzBuzz. Nous allons donc choisir ma préférée :

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)
  • Itérer de 1 à 100, en utilisant une plage de 101.
  • Pour chaque nombre, calculer le module (reste après division) pour 3 et 5.
  • Si le reste est 0, alors le nombre est un multiple du facteur donné.
    • Si le reste est 0 pour 15, alors imprimer FizzBuzz.
    • Si le reste est 0 pour 3, alors imprimer Fizz.
    • Si le reste est 0 pour 5, alors imprimer Buzz.
  • Sinon, il suffit d’imprimer le nombre.

Suivre étape par étape

Pour suivre ce tutoriel étape par étape, vous devrez installer Python et installer pipenv.

🐰 Le code source de cet article est disponible sur GitHub.

Créez un fichier Python nommé game.py, et définissez son contenu avec l’implémentation FizzBuzz ci-dessus.

Ensuite, exécutez python game.py. La sortie devrait ressembler à :

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

🐰 Boom ! Vous êtes en train de réussir l’entretien de codage !

Avant d’aller plus loin, il est important de discuter des différences entre le micro-benchmarking et le macro-benchmarking.

Micro-Benchmarking vs Macro-Benchmarking

Il existe deux grandes catégories de benchmarks logiciels : les micro-benchmarks et les macro-benchmarks. Les micro-benchmarks fonctionnent à un niveau similaire aux tests unitaires. Par exemple, un benchmark pour une fonction qui détermine Fizz, Buzz, ou FizzBuzz pour un seul nombre serait un micro-benchmark. Les macro-benchmarks fonctionnent à un niveau similaire aux tests d’intégration. Par exemple, un benchmark pour une fonction qui joue l’ensemble du jeu de FizzBuzz, de 1 à 100, serait un macro-benchmark.

Généralement, il est préférable de tester au niveau le plus bas d’abstraction possible. Dans le cas des benchmarks, cela les rend à la fois plus faciles à maintenir, et cela aide à réduire le bruit dans les mesures. Cependant, tout comme avoir des tests de bout en bout peut être très utile pour vérifier la cohérence de l’ensemble du système tel que prévu, avoir des macro-benchmarks peut être très utile pour s’assurer que les chemins critiques à travers votre logiciel restent performants.

Benchmarking en Python

Les deux options populaires pour le benchmarking en Python sont : pytest-benchmark et airspeed velocity (asv)

pytest-benchmark est un outil de benchmarking puissant intégré au populaire cadre de test pytest. Il permet aux développeurs de mesurer et de comparer la performance de leur code en exécutant des benchmarks en parallèle de leurs tests unitaires. Les utilisateurs peuvent facilement comparer leurs résultats de benchmark localement et exporter leurs résultats dans divers formats, tels que JSON.

airspeed velocity (asv) est un autre outil de benchmarking avancé dans l’écosystème Python. L’un des principaux avantages de asv est sa capacité à générer des rapports HTML détaillés et interactifs, ce qui facilite la visualisation des tendances de performances et l’identification des régressions. De plus, asv prend en charge le Benchmarking Continu Relatif nativement.

Les deux sont supportés par Bencher. Alors, pourquoi choisir pytest-benchmark ? pytest-benchmark s’intègre parfaitement avec pytest, qui est le standard de facto pour les tests unitaires dans l’écosystème Python. Je vous suggère d’utiliser pytest-benchmark pour évaluer la latence de votre code, surtout si vous utilisez déjà pytest. En effet, pytest-benchmark est excellent pour mesurer le temps d’horloge.

Refactoriser FizzBuzz

Afin de tester notre application FizzBuzz, nous devons découpler notre logique de l’exécution principale de notre programme. Les outils de benchmark ne peuvent pas mesurer l’exécution principale. Pour ce faire, nous devons apporter quelques modifications.

Refactorisons notre logique FizzBuzz en quelques fonctions :

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 : Prend un entier n, appelle fizz_buzz avec ce nombre, et si should_print est True, imprime le résultat.
  • fizz_buzz : Prend un entier n et effectue la logique réelle de Fizz, Buzz, FizzBuzz, ou juste le nombre, renvoyant le résultat sous forme de chaîne.

Ensuite, mettez à jour l’exécution principale pour qu’elle ressemble à ceci :

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

L’exécution principale de notre programme parcourt les nombres de 1 à 100 inclus et appelle play_game pour chaque nombre, avec should_print défini sur True.

Évaluation comparative de FizzBuzz

Afin de faire l’évaluation comparative de notre code, nous devons créer une fonction de test qui exécute notre benchmark. En bas de game.py, ajoutez le code suivant :

def test_game(benchmark):
def run_game():
for i in range(1, 101):
play_game(i, False)
benchmark(run_game)
  • Créez une fonction nommée test_game qui prend en paramètre un fixture benchmark de pytest-benchmark.
  • Créez une fonction run_game qui itère de 1 à 100 inclusivement.
    • Pour chaque nombre, appelez play_game, avec should_print défini sur False.
  • Passez la fonction run_game au coureur benchmark.

Nous devons maintenant configurer notre projet pour exécuter nos benchmarks.

Créez un nouvel environnement virtuel avec pipenv :

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

Installez pytest-benchmark dans ce nouvel environnement 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)...

Nous sommes maintenant prêts à évaluer notre code, exécutez 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 =========================================================

🐰 Laitue faire bouger les betteraves ! Nous avons nos premières métriques de benchmark !

Enfin, nous pouvons reposer nos têtes de développeurs fatiguées… Je plaisante, nos utilisateurs veulent une nouvelle fonctionnalité !

Écrire FizzBuzzFibonacci en Python

Nos indicateurs clés de performance (KPI) sont en baisse, donc notre Chef de Produit (PM) veut que nous ajoutions une nouvelle fonctionnalité. Après de nombreux brainstormings et entretiens avec les utilisateurs, il est décidé que le bon vieux FizzBuzz ne suffit pas. Les enfants d’aujourd’hui veulent un nouveau jeu, FizzBuzzFibonacci.

Les règles du FizzBuzzFibonacci sont les suivantes :

Écrivez un programme qui imprime les entiers de 1 à 100 (inclus) :

  • Pour les multiples de trois, imprimez Fizz
  • Pour les multiples de cinq, imprimez Buzz
  • Pour les multiples de trois et cinq, imprimez FizzBuzz
  • Pour les nombres qui font partie de la séquence de Fibonacci, imprimez uniquement Fibonacci
  • Pour tous les autres, imprimez le nombre

La séquence de Fibonacci est une séquence dans laquelle chaque nombre est la somme des deux précédents. Par exemple, en commençant par 0 et 1, le prochain nombre dans la séquence de Fibonacci serait 1. Suivi par : 2, 3, 5, 8 et ainsi de suite. Les nombres qui font partie de la séquence de Fibonacci sont connus sous le nom de nombres de Fibonacci. Nous allons donc devoir écrire une fonction qui détecte les nombres de Fibonacci.

Il y a plusieurs façons de générer la séquence de Fibonacci et plusieurs façons de détecter un nombre de Fibonacci. Nous allons donc choisir ma méthode préférée :

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
  • Créez une fonction nommée is_fibonacci_number qui prend en entrée un entier et retourne un booléen.
  • Itérez sur tous les nombres de 0 jusqu’à notre nombre n inclus.
  • Initialisez notre séquence de Fibonacci en commençant par 0 et 1 comme étant respectivement les nombres previous et current.
  • Itérez tant que le nombre current est inférieur à l’itération actuelle i.
  • Additionnez les nombres previous et current pour obtenir le nombre next_value.
  • Mettez à jour le nombre previous avec le nombre current.
  • Mettez à jour le nombre current avec le nombre next_value.
  • Une fois que current est supérieur ou égal au nombre donné n, nous sortirons de la boucle.
  • Vérifiez si le nombre current est égal au nombre donné n et, le cas échéant, retournez True.
  • Sinon, retournez False.

Nous devons maintenant mettre à jour notre fonction 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)
  • Renommez la fonction fizz_buzz en fizz_buzz_fibonacci pour la rendre plus descriptive.
  • Appelez notre fonction d’assistance is_fibonacci_number.
  • Si le résultat de is_fibonacci_number est True, retournez Fibonacci.
  • Si le résultat de is_fibonacci_number est False, réalisez la même logique Fizz, Buzz, FizzBuzz ou nombre et retournez le résultat.

Comme nous avons renommé fizz_buzz en fizz_buzz_fibonacci, nous devons également mettre à jour notre fonction play_game :

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

Tant notre exécution principale que la fonction test_game peuvent rester exactement les mêmes.

Mesurer les performances de FizzBuzzFibonacci

Maintenant, nous pouvons relancer notre test de performance :

$ 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 =========================================================

En remontant dans notre historique de terminal, nous pouvons faire une comparaison visuelle entre les performances de nos jeux FizzBuzz et FizzBuzzFibonacci : 10.8307 us vs 735.5682 us. Vos chiffres seront légèrement différents des miens. Cependant, la différence entre les deux jeux est probablement d’environ 50 fois. Cela me semble bien ! Surtout pour ajouter une fonctionnalité aussi impressionnante que Fibonacci à notre jeu. Les enfants vont adorer !

Étendre FizzBuzzFibonacci en Python

Notre jeu est un succès ! Les enfants adorent vraiment jouer à FizzBuzzFibonacci. Tellement que les dirigeants ont décidé qu’ils voulaient une suite. Mais c’est le monde moderne, nous avons besoin de revenus récurrents annuels (ARR) et non plus d’achats uniques ! La nouvelle vision pour notre jeu est qu’il soit ouvert, terminé de vivre entre la limite de 1 et 100 (même si elle est incluse). Non, nous explorons de nouveaux horizons !

Les règles pour Open World FizzBuzzFibonacci sont les suivantes :

Écrivez un programme qui prend en entrée n’importe quel nombre entier positif et affiche :

  • Pour les multiples de trois, affichez Fizz
  • Pour les multiples de cinq, affichez Buzz
  • Pour les multiples à la fois de trois et de cinq, affichez FizzBuzz
  • Pour les nombres qui font partie de la séquence de Fibonacci, affichez uniquement Fibonacci
  • Pour tous les autres, affichez le nombre

Pour que notre jeu fonctionne pour n’importe quel nombre, nous devrons accepter un argument de la ligne de commande. Mettez à jour l’exécution principale pour qu’elle ressemble à ceci :

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...")
  • Importez le paquet sys.
  • Récupérez tous les arguments (args) passés à notre jeu depuis la ligne de commande.
  • Obtenez le premier argument passé à notre jeu et vérifiez s’il s’agit d’un chiffre.
    • Si c’est le cas, analysez le premier argument en tant qu’entier, i.
    • Jouez à notre jeu avec l’entier nouvellement analysé i.
  • Si l’analyse échoue ou si aucun argument n’est passé, par défaut, invitez à entrer une saisie valide.

Maintenant, nous pouvons jouer à notre jeu avec n’importe quel nombre ! Exécutez python game.py suivi d’un nombre entier pour jouer à notre jeu :

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

Et si nous omettons ou fournissons un nombre invalide :

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

Wow, cela a été un test exhaustif ! L’intégration continue (CI) réussit. Nos patrons sont ravis. Livrons-le ! 🚀

La fin


SpongeBob SquarePants Trois semaines plus tard
Meme C'est bien

🐰 … la fin de votre carrière peut-être ?


Rien que pour rire! Tout est en feu! 🔥

Au début, tout semblait aller bien. Et puis à 02h07 du matin le samedi, mon bip a sonné :

📟 Votre jeu est en feu! 🔥

Après me être précipité hors du lit, j’ai essayé de comprendre ce qui se passait. J’ai essayé de rechercher dans les journaux, mais c’était difficile parce que tout continuait de s’effondrer. Finalement, j’ai trouvé le problème. Les enfants ! Ils adorent notre jeu tellement, qu’ils jouaient jusqu’à un million! Dans un éclair de génie, j’ai ajouté deux nouveaux points de référence :

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 pour jouer au jeu avec le nombre cent (100)
  • Un micro-benchmark test_game_1_000_000 pour jouer au jeu avec le nombre un million (1_000_000)

Lorsque je l’ai exécuté, j’ai obtenu cela :

$ 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%]

Attendez… attendez…

-------------------------------------------------------------------------------------------------- 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 =========================================================

Quoi ! 15.8470 us x 1,000 devrait être 15,847.0 us pas 571,684.6334 us 🤯 Même si j’ai obtenu ma fonction de séquence de Fibonacci correctement d’un point de vue fonctionnel, je dois avoir un problème de performance quelque part.

Corriger FizzBuzzFibonacci en Python

Reprenons un autre regard sur cette fonction 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

Maintenant que je pense à la performance, je réalise que j’ai une boucle supplémentaire, inutile. Nous pouvons complètement nous débarrasser de la boucle for i in range(n + 1): et simplement comparer la valeur current au nombre donné (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
  • Mettez à jour notre fonction is_fibonacci_number.
  • Initialisez notre séquence de Fibonacci en commençant par 0 et 1 en tant que nombres previous et current respectivement.
  • Itérez tant que le nombre current est inférieur au nombre donné n.
  • Ajoutez le nombre previous et le nombre current pour obtenir le nombre next_value.
  • Mettez à jour le nombre previous au nombre current.
  • Mettez à jour le nombre current au nombre next_value.
  • Une fois que current est supérieur ou égal au nombre donné n, nous sortirons de la boucle.
  • Vérifiez si le nombre current est égal au nombre donné n et retournez ce résultat.

Maintenant, relançons ces benchmarks et voyons ce que nous obtenons :

$ 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 ! Notre benchmark test_game est de retour à peu près au même niveau que l’original FizzBuzz. J’aimerais me souvenir exactement de quel score il s’agissait. Cela fait trois semaines cependant. Mon historique de terminal ne remonte pas aussi loin. Et pytest-benchmark ne stocke ses résultats que lorsque nous le lui demandons. Mais je pense que c’est proche !

Le benchmark test_game_100 a chuté de près de 50x à 322.0815 ns. Et le benchmark test_game_1_000_000 a chuté de plus de 500,000x ! 571,684,633.4 ns à 753.1445 ns !

🐰 Eh bien, au moins, nous avons détecté ce problème de performance avant qu’il n’arrive en production… oh, d’accord. Peu importe…

Détection des régressions de performances dans l’intégration continue (CI)

Les dirigeants n’étaient pas contents du torrent de critiques négatives que notre jeu a reçu à cause de mon petit bug de performance. Ils m’ont dit de ne pas laisser cela se reproduire, et quand j’ai demandé comment, ils m’ont juste dit de ne pas le refaire. Comment suis-je censé gérer cela‽

Heureusement, j’ai trouvé cet outil open source génial appelé Bencher. Il y a un niveau gratuit super généreux, donc je peux simplement utiliser Bencher Cloud pour mes projets personnels. Et au travail où tout doit être dans notre cloud privé, j’ai commencé à utiliser Bencher Self-Hosted.

Bencher a des adaptateurs intégrés, il est donc facile de l’intégrer dans CI. Après avoir suivi le guide de démarrage rapide, je suis en mesure d’exécuter mes benchmarks et de les suivre avec 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

En utilisant cet astucieux appareil de voyage dans le temps qu’un gentil lapin m’a donné, j’ai pu revenir en arrière et revivre ce qui se serait passé si nous utilisions Bencher depuis le début. Vous pouvez voir où nous avons d’abord poussé l’implémentation buggée de FizzBuzzFibonacci. J’ai immédiatement obtenu des échecs dans CI en commentaire sur ma demande de tirage. Ce même jour, j’ai corrigé le bug de performance, en supprimant cette boucle inutile et supplémentaire. Pas de feux. Juste des utilisateurs heureux.

Bencher: Benchmarking Continu

🐰 Bencher

Bencher est une suite d’outils de benchmarking continu. Avez-vous déjà eu une régression de performance qui a impacté vos utilisateurs ? Bencher aurait pu empêcher cela de se produire. Bencher vous permet de détecter et de prévenir les régressions de performance avant qu’elles n’arrivent en production.

  • Exécuter: Exécutez vos benchmarks localement ou en CI en utilisant vos outils de benchmarking préférés. La CLI bencher enveloppe simplement votre harnais de benchmarking existant et stocke ses résultats.
  • Suivre: Suivez les résultats de vos benchmarks au fil du temps. Surveillez, interrogez et graphiquez les résultats à l’aide de la console web Bencher en fonction de la branche source, du banc d’essai et de la mesure.
  • Détecter: Détectez les régressions de performances en CI. Bencher utilise des analyses de pointe et personnalisables pour détecter les régressions de performances avant qu’elles n’arrivent en production.

Pour les mêmes raisons que les tests unitaires sont exécutés en CI pour prévenir les régressions de fonctionnalités, les benchmarks devraient être exécutés en CI avec Bencher pour prévenir les régressions de performance. Les bugs de performance sont des bugs !

Commencez à détecter les régressions de performances en CI — essayez Bencher Cloud gratuitement.

🤖 Ce document a été automatiquement généré par OpenAI GPT-4. Il peut ne pas être précis et peut contenir des erreurs. Si vous trouvez des erreurs, veuillez ouvrir une issue sur GitHub.