Wie man Python-Code mit pytest-benchmark benchmarket
Everett Pompeii
Was ist Benchmarking?
Benchmarking ist die Praxis, die Leistung Ihres Codes zu testen, um zu sehen, wie schnell (Latenz) oder wie viel (Durchsatz) Arbeit er leisten kann. Dieser oft übersehene Schritt in der Softwareentwicklung ist entscheidend für die Erstellung und Wartung von schnellem und leistungsstarkem Code. Benchmarking liefert die notwendigen Metriken für Entwickler, um zu verstehen, wie gut ihr Code unter verschiedenen Arbeitslasten und Bedingungen funktioniert. Aus den gleichen Gründen, aus denen Sie Unit- und Integrationstests schreiben, um Funktionsregressionen zu verhindern, sollten Sie Benchmarks schreiben, um Leistungsregressionen zu verhindern. Leistungsfehler sind Fehler!
Schreiben Sie FizzBuzz in Python
Um Benchmarks zu schreiben, benötigen wir einige Quellcodes zum Benchmarken. Zum Anfang werden wir ein sehr einfaches Programm schreiben, FizzBuzz.
Die Regeln für FizzBuzz lauten wie folgt:
Schreibe ein Programm, das die ganzen Zahlen von
1
bis100
(inklusiv) ausgibt:
- Für Vielfache von drei, drucke
Fizz
- Für Vielfache von fünf, drucke
Buzz
- Für Vielfache von drei und fünf, drucke
FizzBuzz
- Für alle anderen, drucke die Zahl
Es gibt viele Möglichkeiten, FizzBuzz zu schreiben. Also wählen wir meine Lieblingsmethode:
- Iterieren Sie von
1
bis100
, indem Sie eine Range von101
verwenden. - Berechnen Sie für jede Zahl den Modulus (Rest nach Division) sowohl für
3
als auch für5
. - Wenn der Rest
0
ist, dann ist die Zahl ein Vielfaches des gegebenen Faktors.- Wenn der Rest
0
für15
ist, dann drucken SieFizzBuzz
. - Wenn der Rest
0
für3
ist, dann drucken SieFizz
. - Wenn der Rest
0
für5
ist, dann drucken SieBuzz
.
- Wenn der Rest
- Andernfalls drucken Sie einfach die Zahl.
Schritt-für-Schritt folgen
Um diesem Schritt-für-Schritt-Tutorial folgen zu können, müssen Sie Python installieren und pipenv
installieren.
🐰 Der Quellcode für diesen Beitrag ist auf GitHub verfügbar.
Erstellen Sie eine Python-Datei mit dem Namen game.py
, und setzen Sie deren Inhalt auf die obige FizzBuzz-Implementierung.
Führen Sie dann python game.py
aus. Die Ausgabe sollte folgendermaßen aussehen:
🐰 Boom! Du knackst das Coding-Interview!
Bevor wir weitermachen, ist es wichtig, die Unterschiede zwischen Mikro-Benchmarking und Makro-Benchmarking zu besprechen.
Micro-Benchmarking vs. Macro-Benchmarking
Es gibt zwei Hauptkategorien von Software-Benchmarks: Micro-Benchmarks und Macro-Benchmarks.
Micro-Benchmarks arbeiten auf einer Ebene ähnlich wie Unit-Tests.
Zum Beispiel wäre ein Benchmark für eine Funktion, die Fizz
, Buzz
oder FizzBuzz
für eine einzelne Zahl ermittelt, ein Micro-Benchmark.
Macro-Benchmarks arbeiten auf einer Ebene, die Integrationstests ähnelt.
Zum Beispiel wäre ein Benchmark für eine Funktion, die das gesamte Spiel von FizzBuzz spielt, von 1
bis 100
, ein Macro-Benchmark.
Im Allgemeinen ist es am besten, auf der niedrigstmöglichen Abstraktionsebene zu testen. Im Falle von Benchmarks macht dies sie sowohl leichter zu pflegen, und es hilft, die Menge an Rauschen in den Messungen zu reduzieren. Allerdings können genau wie End-to-End-Tests, die für eine Überprüfung der gesamten Systemzusammenstellung sehr hilfreich sein können, Macro-Benchmarks sehr nützlich sein, um sicherzustellen, dass die kritischen Pfade durch Ihre Software performant bleiben.
Benchmarking in Python
Die zwei populären Optionen für Benchmarking in Python sind: pytest-benchmark und airspeed velocity (asv)
pytest-benchmark
ist ein leistungsstarkes Benchmarking-Tool,
das in das beliebte pytest
Testing Framework integriert ist.
Es ermöglicht Entwicklern, die Leistung ihres Codes zu messen und zu vergleichen, indem Benchmarks parallel zu ihren Unit-Tests durchgeführt werden.
Benutzer können ihre Benchmark-Ergebnisse einfach lokal vergleichen
und ihre Ergebnisse in verschiedenen Formaten exportieren, wie zum Beispiel JSON.
airspeed velocity (asv)
ist ein weiteres fortschrittliches Benchmarking-Tool im Python-Ökosystem.
Einer der Hauptvorteile von asv
ist seine Fähigkeit, detaillierte und interaktive HTML-Berichte zu erstellen,
die es einfach machen, Leistungstrends zu visualisieren und Regressionen zu identifizieren.
Außerdem unterstützt asv
von Haus aus Relatives Kontinuierliches Benchmarking.
Beide werden von Bencher unterstützt.
Warum also pytest-benchmark
wählen?
pytest-benchmark
integriert sich nahtlos mit pytest
,
das de facto Standard-Unit-Test-Framework im Python-Ökosystem.
Ich würde vorschlagen, pytest-benchmark
zu verwenden, um die Latenz Ihres Codes zu benchmarken,
insbesondere wenn Sie bereits pytest
verwenden.
pytest-benchmark
eignet sich hervorragend zum Messen der Echtzeit.
Refaktorisierung von FizzBuzz
Um unsere FizzBuzz-Anwendung zu testen, müssen wir unsere Logik von der Hauptexecution unseres Programms entkoppeln. Benchmark-Testumgebungen können die Hauptexecution nicht benchmarken. Um dies zu erreichen, müssen wir ein paar Änderungen vornehmen.
Lassen Sie uns unsere FizzBuzz-Logik in ein paar Funktionen umstrukturieren:
play_game
: Nimmt eine ganze Zahln
, ruftfizz_buzz
mit dieser Zahl auf, und wennshould_print
True
ist, wird das Ergebnis gedruckt.fizz_buzz
: Nimmt eine ganze Zahln
und führt die tatsächlicheFizz
,Buzz
,FizzBuzz
oder Zahlen-Logik aus und gibt das Ergebnis als String zurück.
Aktualisieren Sie dann die Hauptexecution, sodass sie folgendermaßen aussieht:
Die Hauptexecution unseres Programms iteriert durch die Zahlen von 1
bis 100
inklusive und ruft play_game
für jede Zahl auf, wobei should_print
auf True
gesetzt ist.
Benchmarking von FizzBuzz
Um unseren Code zu benchmarken, müssen wir eine Testfunktion erstellen, die unseren Benchmark ausführt. Fügen Sie am Ende von game.py
den folgenden Code hinzu:
- Erstellen Sie eine Funktion namens
test_game
, die einpytest-benchmark
-benchmark
-Fixture annimmt. - Erstellen Sie eine Funktion
run_game
, die von1
bis einschließlich100
iteriert.- Rufen Sie für jede Zahl
play_game
auf, wobeishould_print
aufFalse
gesetzt ist.
- Rufen Sie für jede Zahl
- Übergeben Sie die
run_game
-Funktion dembenchmark
-Runner.
Nun müssen wir unser Projekt so konfigurieren, dass es unsere Benchmarks ausführt.
Erstellen Sie eine neue virtuelle Umgebung mit pipenv
:
Installieren Sie pytest-benchmark
in dieser neuen pipenv
-Umgebung:
Jetzt sind wir bereit, unseren Code zu benchmarken: Führen Sie pytest game.py
aus:
🐰 Lass uns den Beat verwandeln! Wir haben unsere ersten Benchmark-Metriken!
Schließlich können wir unsere müden Entwicklerköpfe ausruhen… Nur ein Scherz, unsere Benutzer wollen ein neues Feature!
Schreiben Sie FizzBuzzFibonacci in Python
Unsere Key Performance Indicators (KPIs) sind gesunken, also möchte unser Product Manager (PM), dass wir ein neues Feature hinzufügen. Nach viel Brainstorming und vielen Nutzerinterviews wurde beschlossen, dass der altbekannte FizzBuzz nicht ausreicht. Die Kinder von heute möchten ein neues Spiel, FizzBuzzFibonacci.
Die Regeln für FizzBuzzFibonacci lauten wie folgt:
Schreibe ein Programm, welches die Zahlen von
1
bis100
(inklusiv) ausgibt:
- Für Vielfache von drei, drucke
Fizz
- Für Vielfache von fünf, drucke
Buzz
- Für Vielfache von sowohl drei als auch fünf, drucke
FizzBuzz
- Für Zahlen, die Teil der Fibonacci-Sequenz sind, drucke nur
Fibonacci
- Für alle anderen, drucke die Zahl
Die Fibonacci-Sequenz ist eine Sequenz, bei der jede Zahl die Summe der beiden vorhergehenden Zahlen ist.
Zum Beispiel wäre, beginnend bei 0
und 1
, die nächste Zahl in der Fibonacci-Sequenz 1
.
Gefolgt von: 2
, 3
, 5
, 8
und so weiter.
Zahlen, die Teil der Fibonacci-Sequenz sind, werden als Fibonacci-Zahlen bezeichnet. Daher müssen wir eine Funktion schreiben, die Fibonacci-Zahlen erkennt.
Es gibt viele Arten, die Fibonacci-Sequenz zu schreiben und ebenso viele Möglichkeiten, eine Fibonacci-Zahl zu erkennen. Daher werden wir meine Lieblingsmethode wählen:
- Erstellen Sie eine Funktion namens
is_fibonacci_number
, die eine ganze Zahl entgegennimmt und einen booleschen Wert zurückgibt. - Iterieren Sie für alle Zahlen von
0
bis zu unserer gegebenen Zahln
inklusive. - Initialisieren Sie unsere Fibonacci-Sequenz, beginnend mit
0
und1
alsprevious
undcurrent
Zahlen. - Iterieren Sie, während die
current
Zahl kleiner ist als die aktuelle Iterationi
. - Addieren Sie die
previous
undcurrent
Zahl, um dienext_value
Zahl zu erhalten. - Aktualisieren Sie die
previous
Zahl auf diecurrent
Zahl. - Aktualisieren Sie die
current
Zahl auf dienext_value
Zahl. - Sobald
current
größer oder gleich der gegebenen Zahln
ist, beenden wir die Schleife. - Prüfen Sie, ob die
current
Zahl gleich der gegebenen Zahln
ist, und falls ja, geben SieTrue
zurück. - Andernfalls geben Sie
False
zurück.
Jetzt müssen wir unsere fizz_buzz
Funktion aktualisieren:
- Benennen Sie die
fizz_buzz
Funktion infizz_buzz_fibonacci
um, um sie genauer zu beschreiben. - Rufen Sie unsere
is_fibonacci_number
Hilfsfunktion auf. - Wenn das Ergebnis von
is_fibonacci_number
True
ist, geben SieFibonacci
zurück. - Wenn das Ergebnis von
is_fibonacci_number
False
ist, führen Sie die gleicheFizz
,Buzz
,FizzBuzz
oder Zahlenlogik aus und geben das Ergebnis zurück.
Da wir fizz_buzz
in fizz_buzz_fibonacci
umbenannt haben, müssen wir auch unsere play_game
Funktion aktualisieren:
Sowohl unsere Hauptexekution als auch die test_game
Funktion können genau gleich bleiben.
Benchmarking FizzBuzzFibonacci
Nun können wir unseren Benchmark erneut durchführen:
Wenn wir durch unsere Terminal-Historie zurückblättern,
können wir einen ungefähren Vergleich zwischen der Leistung unserer FizzBuzz- und FizzBuzzFibonacci-Spiele machen: 10.8307 us
vs 735.5682 us
.
Ihre Zahlen werden ein wenig anders sein als meine.
Der Unterschied zwischen den beiden Spielen liegt jedoch wahrscheinlich im Bereich von 50x.
Das erscheint mir gut! Besonders wenn man bedenkt, dass wir ein so beeindruckend klingendes Feature wie Fibonacci zu unserem Spiel hinzugefügt haben.
Die Kinder werden es lieben!
FizzBuzzFibonacci in Python erweitern
Unser Spiel ist ein Hit! Die Kinder lieben es tatsächlich, FizzBuzzFibonacci zu spielen.
So sehr, dass das Management beschlossen hat, dass sie eine Fortsetzung wollen.
Aber das ist die moderne Welt, wir brauchen jährliche wiederkehrende Einnahmen (ARR), keine Einmalkäufe!
Die neue Vision für unser Spiel ist, dass es offen gestaltet ist, keine Begrenzung mehr zwischen 1
und 100
(selbst wenn sie inklusive ist).
Nein, wir bewegen uns zu neuen Horizonten!
Die Regeln für Open World FizzBuzzFibonacci lauten wie folgt:
Schreiben Sie ein Programm, das jede positive ganze Zahl akzeptiert und ausgibt:
- Für Vielfache von drei, drucken Sie
Fizz
- Für Vielfache von fünf, drucken Sie
Buzz
- Für Vielfache von drei und fünf, drucken Sie
FizzBuzz
- Für Zahlen, die Teil der Fibonacci-Sequenz sind, drucken Sie nur
Fibonacci
- Für alle anderen drucken Sie die Zahl
Um unser Spiel für jede beliebige Zahl spielbar zu machen, müssen wir ein Kommandozeilenargument akzeptieren. Aktualisieren Sie die Hauptroutine, um folgendermaßen auszusehen:
- Importieren Sie das
sys
-Paket. - Sammeln Sie alle Argumente (
args
), die über die Kommandozeile an unser Spiel übergeben werden. - Nehmen Sie das erste Argument, das an unser Spiel übergeben wird, und prüfen Sie, ob es eine Ziffer ist.
- Wenn ja, parsen Sie das erste Argument als Ganzzahl,
i
. - Spielen Sie unser Spiel mit der neu geparsten Ganzzahl
i
.
- Wenn ja, parsen Sie das erste Argument als Ganzzahl,
- Falls das Parsen fehlschlägt oder kein Argument übergeben wird, fordern Sie standardmäßig eine gültige Eingabe an.
Jetzt können wir unser Spiel mit jeder Zahl spielen!
Führen Sie python game.py
gefolgt von einer Ganzzahl aus, um unser Spiel zu spielen:
Und wenn wir eine Zahl weglassen oder eine ungültige Zahl angeben:
Wow, das war ein gründlicher Test! CI besteht. Unsere Chefs sind begeistert. Lassen Sie es uns veröffentlichen! 🚀
Das Ende
🐰 … das Ende Ihrer Karriere vielleicht?
Nur ein Scherz! Alles steht in Flammen! 🔥
Nun, anfangs schien alles gut zu laufen. Und dann um 02:07 Uhr am Samstag löste mein Pager aus:
📟 Dein Spiel steht in Flammen! 🔥
Nachdem ich aus dem Bett gehetzt war, versuchte ich herauszufinden, was los war. Ich versuchte, die Logs durchzusuchen, aber das war schwierig, weil alles ständig abstürzte. Schließlich fand ich das Problem. Die Kinder! Sie liebten unser Spiel so sehr, dass sie es bis zu einer Million hochspielten! In einem Geistesblitz fügte ich zwei neue Benchmarks hinzu:
- Ein Mikro-Benchmark
test_game_100
zum Spielen des Spiels mit der Zahl einhundert (100
) - Ein Mikro-Benchmark
test_game_1_000_000
zum Spielen des Spiels mit der Zahl eine Million (1_000_000
)
Als ich es ausführte, bekam ich das:
Warten Sie… warten Sie…
Was! 15.8470 us
x 1,000
sollte 15,847.0 us
sein, nicht 571,684.6334 us
🤯
Obwohl mein Fibonacci-Sequenzcode funktional korrekt ist, muss irgendwo ein Leistungsproblem vorliegen.
Fix FizzBuzzFibonacci in Python
Werfen wir einen weiteren Blick auf die is_fibonacci_number
-Funktion:
Jetzt, wo ich über die Leistung nachdenke, merke ich, dass ich eine unnötige, zusätzliche Schleife habe. Wir können die Schleife for i in range(n + 1):
komplett entfernen und einfach den Wert current
mit der gegebenen Zahl (n
) vergleichen 🤦
- Aktualisieren Sie unsere
is_fibonacci_number
-Funktion. - Initialisieren Sie unsere Fibonacci-Sequenz beginnend mit
0
und1
als dieprevious
undcurrent
Zahlen. - Iterieren Sie, während die
current
Zahl kleiner als die gegebene Zahln
ist. - Fügen Sie die
previous
undcurrent
Zahl hinzu, um dienext_value
Zahl zu erhalten. - Aktualisieren Sie die
previous
Zahl zu dercurrent
Zahl. - Aktualisieren Sie die
current
Zahl zu dernext_value
Zahl. - Sobald
current
größer oder gleich der gegebenen Zahln
ist, wird die Schleife beendet. - Überprüfen Sie, ob die
current
Zahl gleich der gegebenen Zahln
ist und geben Sie das Ergebnis zurück.
Lassen Sie uns nun diese Benchmarks erneut ausführen und sehen, wie wir abgeschnitten haben:
Oh, wow! Unser test_game
Benchmark ist wieder etwa dort, wo er ursprünglich für FizzBuzz war.
Ich wünschte, ich könnte mich genau erinnern, was dieser Wert war. Aber es ist nun drei Wochen her.
Mein Terminalverlauf geht nicht so weit zurück.
Und pytest-benchmark
speichert seine Ergebnisse nur, wenn wir es darum bitten.
Aber ich denke, es ist nah dran!
Der test_game_100
Benchmark ist fast 50x auf 322.0815 ns
gesunken.
Und der test_game_1_000_000
Benchmark ist mehr als 500.000x gesunken! 571,684,633.4 ns
zu 753.1445 ns
!
🐰 Hey, wenigstens haben wir diesen Leistungsfehler entdeckt, bevor es in die Produktion gelangt… oh, richtig. Schon gut…
Leistungsverschlechterungen in CI auffangen
Die Geschäftsführung war nicht glücklich über die Flut von negativen Bewertungen, die unser Spiel aufgrund meines kleinen Performance-Bugs erhalten hat. Sie sagten mir, dass so etwas nicht wieder passieren darf, und als ich fragte wie, sagten sie mir einfach, dass ich es einfach nicht noch einmal tun soll. Wie soll ich das schaffen‽
Zum Glück habe ich dieses großartige Open-Source-Tool namens Bencher gefunden. Es gibt einen sehr großzügigen kostenlosen Tier, sodass ich Bencher Cloud einfach für meine persönlichen Projekte verwenden kann. Und bei der Arbeit, wo alles in unserer privaten Cloud sein muss, habe ich angefangen, Bencher Self-Hosted zu verwenden.
Bencher hat eingebaute Adapter, also ist es einfach, es in CI zu integrieren. Nachdem ich der Quick Start Anleitung gefolgt bin, kann ich meine Benchmarks ausführen und mit Bencher verfolgen.
Mit diesem praktischen Zeitreise-Gerät, das mir ein netter Hase gegeben hat, konnte ich zurück in die Vergangenheit reisen und nachspielen, was passiert wäre, wenn wir von Anfang an Bencher verwendet hätten. Sie können sehen, wo wir die fehlerhafte FizzBuzzFibonacci Implementierung zum ersten Mal gepusht haben. Ich habe sofort Fehler in CI als Kommentar zu meinem Pull Request bekommen. Noch am gleichen Tag habe ich den Performance-Fehler behoben, indem ich diese unnötige, zusätzliche Schleife entfernt habe. Keine Brände. Nur zufriedene Benutzer.
Bencher: Kontinuierliches Benchmarking
Bencher ist eine Suite von kontinuierlichen Benchmarking-Tools. Hatten Sie jemals eine Performance Regression, die Ihre Nutzer beeinflusste? Bencher hätte das verhindern können. Bencher ermöglicht es Ihnen, Leistungsregressionen vorher zu erkennen und zu verhindern, bevor sie in die Produktion gelangen.
- Ausführen: Führen Sie Ihre Benchmarks lokal oder in CI mit Ihren bevorzugten Benchmarking-Tools aus. Das
bencher
CLI umfasst einfach Ihr vorhandenes Benchmark-Harness und speichert die Ergebnisse. - Verfolgen: Verfolgen Sie die Ergebnisse Ihrer Benchmarks im Laufe der Zeit. Überwachen, abfragen und grafisch darstellen der Ergebnisse mit der Bencher Web Konsole auf Basis des Quellzweigs, Testbetts und Maßnahme.
- Auffangen: Fangen Sie Leistungsregressionen in CI ab. Bencher verwendet modernste, anpassbare Analysen, um Leistungsregressionen zu erkennen, bevor sie in die Produktion gelangen.
Aus denselben Gründen, warum Unit Tests in CI laufen, um Feature Regressionen zu verhindern, sollten Benchmarks in CI mit Bencher ausgeführt werden, um Leistungsregressionen zu verhindern. Performance-Bugs sind Fehler!
Beginnen Sie damit, Leistungsregressionen in CI aufzufangen - probieren Sie Bencher Cloud kostenlos aus.