Wie man C++-Code mit Google Benchmark benchmarkt
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!
Schreibe FizzBuzz in C++
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
1bis100(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:
#include <iostream>
int main(){ for (int i = 1; i <= 100; i++) { if ((i % 15) == 0) std::cout << "FizzBuzz\n"; else if ((i % 3) == 0) std::cout << "Fizz\n"; else if ((i % 5) == 0) std::cout << "Buzz\n"; else std::cout << i << "\n"; } return 0;}- Iterieren Sie von
1bis100, und erhöhen Sie nach jeder Iteration. - Berechnen Sie für jede Zahl den Modulus (Rest nach der Division).
- Wenn der Rest
0ist, dann ist die Zahl ein Vielfaches des gegebenen Faktors:- Wenn der Rest bei
150ist, dann drucken SieFizzBuzz. - Wenn der Rest bei
30ist, dann drucken SieFizz. - Wenn der Rest bei
50ist, dann drucken SieBuzz.
- Wenn der Rest bei
- Ansonsten drucken Sie einfach die Zahl.
Schritt-für-Schritt-Anleitung
Um dieser Schritt-für-Schritt-Anleitung zu folgen, müssen Sie installieren Sie git, installieren Sie cmake, und installieren Sie die GNU Compiler Collection (GCC) g++.
🐰 Der Quellcode für diesen Beitrag ist auf GitHub verfügbar.
Erstellen Sie eine C++-Datei mit dem Namen game.cpp,
und fügen Sie den Inhalt der obigen FizzBuzz-Implementierung ein.
Verwenden Sie g++, um eine ausführbare Datei mit dem Namen game zu erstellen, und führen Sie sie dann aus.
Die Ausgabe sollte wie folgt aussehen:
$ g++ -std=c++11 game.cpp -o game && ./game12Fizz4BuzzFizz78FizzBuzz11Fizz1314FizzBuzz...9798FizzBuzz🐰 Boom! Sie meistern das Coding-Interview!
Bevor Sie weitergehen, ist es wichtig, die Unterschiede zwischen Mikro-Benchmarking und Makro-Benchmarking zu diskutieren.
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 C++
Die zwei beliebten Optionen für Benchmarking in C++ sind: Google Benchmark und Catch2.
Google Benchmark ist eine robuste und vielseitige Benchmarking-Bibliothek für C++, die es Entwicklern ermöglicht, die Leistung ihres Codes mit hoher Präzision zu messen. Ein wesentlicher Vorteil ist die einfache Integration in bestehende Projekte, insbesondere solche, die bereits GoogleTest verwenden. Google Benchmark bietet detaillierte Leistungsmetriken, einschließlich der Möglichkeit zur Messung von CPU-Zeit, Wall-Zeit und Speicherverbrauch. Es unterstützt ein breites Spektrum an Benchmarking-Szenarien, von einfachen Funktionsbenchmarks bis hin zu komplexen, parametrisierten Tests.
Catch2 ist ein modernes, header-only Testframework für C++, das den Prozess des Schreibens und Ausführens von Tests vereinfacht. Einer seiner Hauptvorteile ist die Benutzerfreundlichkeit, mit einer Syntax, die sowohl intuitiv als auch ausdrucksstark ist, was es Entwicklern ermöglicht, Tests schnell und klar zu schreiben. Catch2 unterstützt eine breite Palette von Testtypen, einschließlich Unit-Tests, Integrationstests, Behavior-Driven Development (BDD) Stiltests und grundlegende Mikro-Benchmarking-Funktionen.
Beide werden von Bencher unterstützt. Warum also Google Benchmark wählen? Google Benchmark integriert sich nahtlos mit GoogleTest, welches der De-facto-Standard für Unit-Tests im C++-Ökosystem ist. Ich würde vorschlagen, Google Benchmark zu verwenden, um die Latenz Ihres Codes zu messen, insbesondere wenn Sie bereits GoogleTest verwenden. Das heißt, Google Benchmark eignet sich hervorragend zum Messen der echten Zeit.
Refactorisieren von FizzBuzz
Um unsere FizzBuzz-Anwendung zu testen,
müssen wir unsere Logik von der main-Funktion unseres Programms entkoppeln.
Benchmark-Frameworks können die main-Funktion nicht benchmarken.
Um dies zu tun, müssen wir einige Änderungen vornehmen.
Lassen Sie uns unsere FizzBuzz-Logik in ein paar Funktionen umstrukturieren,
die sich in einer neuen Datei namens play_game.cpp befinden:
#include <iostream>#include <string>
std::string fizz_buzz(int n) { if (n % 15 == 0) { return "FizzBuzz"; } else if (n % 3 == 0) { return "Fizz"; } else if (n % 5 == 0) { return "Buzz"; } else { return std::to_string(n); }}
void play_game(int n, bool should_print) { std::string result = fizz_buzz(n); if (should_print) { std::cout << result << std::endl; }}fizz_buzz: Nimmt eine ganze Zahlnentgegen und führt die eigentlicheFizz,Buzz,FizzBuzzoder Zahlenlogik durch, indem das Ergebnis als String zurückgegeben wird.play_game: Nimmt eine ganze Zahlnentgegen, ruftfizz_buzzmit dieser Zahl auf und gibt das Ergebnis aus, wennshould_printauftruegesetzt ist.
Erstellen wir nun eine Header-Datei mit dem Namen play_game.h und fügen die Funktionsdeklaration von play_game hinzu:
#ifndef GAME_H#define GAME_H
#include <string>
void play_game(int n, bool should_print);
#endif // GAME_HDann aktualisieren Sie die main-Funktion in game.cpp, um die Definition der play_game-Funktion aus der Header-Datei zu verwenden:
#include "play_game.h"
int main(){ for (int i = 1; i <= 100; i++) { play_game(i, true); }}Die main-Funktion unseres Programms iteriert durch die Zahlen 1 bis 100 inklusive und ruft für jede Zahl play_game auf, wobei should_print auf true gesetzt ist.
Benchmarking FizzBuzz
Um unseren Code zu benchmarken, müssen wir zuerst Google Benchmark installieren.
Klonen Sie die Bibliothek:
$ git clone https://github.com/google/benchmark.gitWechseln Sie in das neu geklonte Verzeichnis:
$ cd benchmarkVerwenden Sie cmake, um ein Verzeichnis für die Erstellung zu erstellen, in das das Build-Output platziert wird:
$ cmake -E make_directory "build"Verwenden Sie cmake, um die Build-Systemdateien zu generieren und alle Abhängigkeiten herunterzuladen:
$ cmake -E chdir "build" cmake -DBENCHMARK_DOWNLOAD_DEPENDENCIES=on -DCMAKE_BUILD_TYPE=Release ../Endlich die Bibliothek bauen:
$ cmake --build "build" --config ReleaseZurück zum übergeordneten Verzeichnis gehen:
cd ..Erstellen wir nun eine neue Datei namens benchmark_game.cpp:
#include "play_game.h"#include <benchmark/benchmark.h>#include <iostream>
static void BENCHMARK_game(benchmark::State &state){ for (auto _ : state) { for (int i = 1; i <= 100; i++) { play_game(i, false); } }}
BENCHMARK(BENCHMARK_game);
BENCHMARK_MAIN();- Importieren Sie die Funktionsdefinitionen aus
play_game.h. - Importieren Sie den Header der Google
benchmark-Bibliothek. - Erstellen Sie eine Funktion namens
BENCHMARK_game, die eine Referenz zubenchmark::Statenimmt. - Iterieren Sie über das
benchmark::State-Objekt. - Für jede Iteration von
1bis100(inklusive) iterieren.- Rufen Sie
play_gamemit der aktuellen Nummer undshould_printauffalseauf.
- Rufen Sie
- Übergeben Sie die Funktion
BENCHMARK_gamean denBENCHMARKRunner. - Führen Sie den Benchmark mit
BENCHMARK_MAINaus.
Jetzt sind wir bereit, unseren Code zu benchmarken:
$ g++ -std=c++11 -isystem benchmark/include -Lbenchmark/build/src -lbenchmark -lpthread play_game.cpp benchmark_game.cpp -o benchmark_game && ./benchmark_game2023-10-16T14:00:00-04:00Running ./benchmark_gameRun on (8 X 24 MHz CPU s)CPU Caches: L1 Data 64 KiB L1 Instruction 128 KiB L2 Unified 4096 KiB (x8)Load Average: 5.55, 4.62, 4.69---------------------------------------------------------Benchmark Time CPU Iterations---------------------------------------------------------BENCHMARK_game 1698 ns 1688 ns 419979🐰 Let’s make the beet rocken! Wir haben unsere ersten Benchmark-Metriken!
Endlich können wir unsere müden Entwicklerköpfe ausruhen… Nur ein Scherz, unsere Benutzer wollen ein neues Feature!
Schreiben Sie FizzBuzzFibonacci in C++
Unsere Key Performance Indicators (KPIs) sind gesunken, also möchte unser Product Manager (PM), dass wir eine neue Funktion hinzufügen. Nach viel Brainstorming und vielen Benutzerinterviews wurde entschieden, dass das gute alte FizzBuzz nicht genug ist. Die Kinder von heute wollen ein neues Spiel, FizzBuzzFibonacci.
Die Regeln für FizzBuzzFibonacci lauten wie folgt:
Schreibe ein Programm, welches die Zahlen von
1bis100(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:
bool is_fibonacci_number(int n){ for (int i = 0; i <= n; ++i) { int previous = 0, current = 1; while (current < i) { int next = previous + current; previous = current; current = next; } if (current == n) { return true; } } return false;}- Erstellen Sie eine Funktion namens
is_fibonacci_number, die eine ganze Zahl entgegennimmt und einen booleschen Wert zurückgibt. - Iterieren Sie über alle Zahlen von
0bis zu unserer gegebenen Zahlneinschließlich. - Initiieren Sie unsere Fibonacci-Sequenz beginnend mit
0und1als dievorherigeundaktuelleZahl. - Iterieren Sie, während die
aktuelleZahl kleiner als die aktuelle Iterationiist. - Addieren Sie die
vorherigeundaktuelleZahl, um dienächsteZahl zu erhalten. - Aktualisieren Sie die
vorherigeZahl zuraktuellenZahl. - Aktualisieren Sie die
aktuelleZahl zurnächstenZahl. - Sobald
aktuellgrößer oder gleich der gegebenen Zahlnist, verlassen wir die Schleife. - Prüfen Sie, ob die
aktuelleZahl gleich der gegebenen Zahlnist, und wenn ja, geben Sietruezurück. - Andernfalls geben Sie
falsezurück.
Jetzt müssen wir unsere fizz_buzz Funktion aktualisieren:
std::string fizz_buzz_fibonacci(int n){ if (is_fibonacci_number(n)) { return "Fibonacci"; } else if (n % 15 == 0) { return "FizzBuzz"; } else if (n % 3 == 0) { return "Fizz"; } else if (n % 5 == 0) { return "Buzz"; } else { return std::to_string(n); }}- Benennen Sie die
fizz_buzzFunktion infizz_buzz_fibonaccium, um sie aussagekräftiger zu machen. - Rufen Sie unsere
is_fibonacci_numberHilfsfunktion auf. - Wenn das Ergebnis von
is_fibonacci_numbertrueist, geben SieFibonaccizurück. - Wenn das Ergebnis von
is_fibonacci_numberfalseist, führen Sie die gleicheFizz,Buzz,FizzBuzzoder 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:
void play_game(int n, bool should_print) { std::string result = fizz_buzz_fibonacci(n); if (should_print) { std::cout << result << std::endl; }}Sowohl unsere main Funktion als auch die BENCHMARK_game Funktion können genauso bleiben.
Benchmarking FizzBuzzFibonacci
Nun können wir unseren Benchmark erneut ausführen:
$ g++ -std=c++11 -isystem benchmark/include -Lbenchmark/build/src -lbenchmark -lpthread play_game.cpp benchmark_game.cpp -o benchmark_game && ./benchmark_game2023-10-16T15:00:00-04:00Running ./benchmark_gameRun on (8 X 24 MHz CPU s)CPU Caches: L1 Data 64 KiB L1 Instruction 128 KiB L2 Unified 4096 KiB (x8)Load Average: 4.34, 5.75, 4.71---------------------------------------------------------Benchmark Time CPU Iterations---------------------------------------------------------BENCHMARK_game 56190 ns 56054 ns 12280Wenn wir durch unsere Terminal-Historie zurückscrollen, können wir einen augenscheinlichen Vergleich zwischen der Leistung unserer FizzBuzz- und FizzBuzzFibonacci-Spiele anstellen: 1698 ns vs 56190 ns. Deine Zahlen werden ein wenig anders sein als meine. Der Unterschied zwischen den beiden Spielen liegt jedoch wahrscheinlich im Bereich von 50x. Das scheint mir gut zu sein! Besonders für das Hinzufügen eines Features, das so schick klingt wie Fibonacci zu unserem Spiel. Die Kinder werden es lieben!
FizzBuzzFibonacci in C++ erweitern
Unser Spiel ist ein Hit! Die Kinder lieben es wirklich, FizzBuzzFibonacci zu spielen.
Tatsächlich haben die Führungskräfte signalisiert, dass sie eine Fortsetzung wollen.
Aber das ist die moderne Welt, wir brauchen Annual Recurring Revenue (ARR) und nicht nur einmalige Käufe!
Die neue Vision für unser Spiel ist, dass es offen ist, keine Lebensbeschränkung mehr zwischen 1 und 100 (auch wenn sie inklusive sind).
Nein, wir sind auf neuen Wegen unterwegs!
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
Damit unser Spiel für jede Zahl funktioniert, müssen wir ein Kommandozeilenargument akzeptieren.
Aktualisieren Sie die main-Funktion, damit sie folgendermaßen aussieht:
#include "play_game.h"#include <iostream>#include <cstdlib>
int main(int argc, char *argv[]){ if (argc > 1 && std::isdigit(argv[1][0])) { int i = std::atoi(argv[1]); play_game(i, true); } else { std::cout << "Please, enter a positive integer to play..." << std::endl; } return 0;}- Aktualisieren Sie die
main-Funktion, umargcundargvzu akzeptieren. - Holen Sie das erste Argument, das unserem Spiel übergeben wird, und überprüfen Sie, ob es sich um eine Ziffer handelt.
- 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,
- Wenn das Parsen fehlschlägt oder kein Argument übergeben wird, wird standardmäßig eine gültige Eingabeaufforderung angezeigt.
Jetzt können wir unser Spiel mit jeder beliebigen Zahl spielen!
Kompilieren Sie unsere game-Ausführungsdatei neu und führen Sie dann
die Ausführungsdatei gefolgt von einer Ganzzahl aus, um unser Spiel zu spielen:
$ g++ -std=c++11 game.cpp play_game.cpp -o game$ ./game 9Fizz$ ./game 10Buzz$ ./game 13FibonacciUnd wenn wir eine ungültige Zahl weglassen oder angeben:
$ ./gamePlease, enter a positive integer to play...$ ./game badPlease, enter a positive integer to play...Wow, das war ein gründliches Testen! 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:
static void BENCHMARK_game_100(benchmark::State &state){ for (auto _ : state) { play_game(100, false); }}
static void BENCHMARK_game_1_000_000(benchmark::State &state){ for (auto _ : state) { play_game(1000000, false); }}
BENCHMARK(BENCHMARK_game_100);BENCHMARK(BENCHMARK_game_1_000_000);- Ein Mikro-Benchmark
BENCHMARK_game_100für das Spiel mit der Zahl hundert (100) - Ein Mikro-Benchmark
BENCHMARK_game_1_000_000für das Spiel mit der Zahl eine Million (1_000_000)
Als ich es ausführte, erhielt ich Folgendes:
$ g++ -std=c++11 -isystem benchmark/include -Lbenchmark/build/src -lbenchmark -lpthread play_game.cpp benchmark_game.cpp -o benchmark_game && ./benchmark_game2023-11-04T03:00:00-04:00Running ./benchmark_gameRun on (8 X 24 MHz CPU s)CPU Caches: L1 Data 64 KiB L1 Instruction 128 KiB L2 Unified 4096 KiB (x8)Load Average: 4.98, 5.75, 4.96-------------------------------------------------------------------Benchmark Time CPU Iterations-------------------------------------------------------------------BENCHMARK_game 75547 ns 59280 ns 12560BENCHMARK_game_100 1249 ns 1243 ns 564689Warte… warte…
BENCHMARK_game_1_000_000 110879642 ns 43628118 ns 17Was! 1,249 ns x 10,000 sollten 12,490,000 ns sein, nicht 110,879,642 ns 🤯
Obwohl ich meinen Fibonacci-Sequenz-Code funktional korrekt hatte, muss irgendwo ein Performance-Fehler drin sein.
Fix FizzBuzzFibonacci in C++
Werfen wir einen weiteren Blick auf die Funktion is_fibonacci_number:
bool is_fibonacci_number(int n){ for (int i = 0; i <= n; ++i) { int previous = 0, current = 1; while (current < i) { int next = previous + current; previous = current; current = next; } if (current == n) { return true; } } return false;}Jetzt, da ich über die Leistung nachdenke, fällt mir auf, dass ich eine unnötige, zusätzliche Schleife habe.
Wir können die Schleife for (int i = 0; i <= n; ++i) vollständig entfernen und
einfach den Wert current mit der gegebenen Zahl (n) vergleichen 🤦
bool is_fibonacci_number(int n){ int previous = 0, current = 1; while (current < n) { int next = previous + current; previous = current; current = next; } return current == n;}- Aktualisieren Sie unsere Funktion
is_fibonacci_number. - Initialisieren Sie unsere Fibonacci-Sequenz, beginnend mit
0und1als den jeweiligenpreviousundcurrentZahlen. - Iterieren Sie, während die
current-Zahl kleiner ist als die gegebene Zahln. - Fügen Sie die
previous- undcurrent-Zahl hinzu, um dienext-Zahl zu erhalten. - Aktualisieren Sie die
previous-Zahl auf diecurrent-Zahl. - Aktualisieren Sie die
current-Zahl auf dienext-Zahl. - Sobald
currentgrößer oder gleich der gegebenen Zahlnist, verlassen wir die Schleife. - Überprüfen Sie, ob die
current-Zahl gleich der gegebenen Zahlnist und geben Sie dieses Ergebnis zurück.
Jetzt lassen Sie uns diese Benchmarks erneut ausführen und sehen, wie wir abgeschnitten haben:
$ g++ -std=c++11 -isystem benchmark/include -Lbenchmark/build/src -lbenchmark -lpthread play_game.cpp benchmark_game.cpp -o benchmark_game && ./benchmark_game2023-11-04T05:00:00-04:00Running ./benchmark_gameRun on (8 X 24 MHz CPU s)CPU Caches: L1 Data 64 KiB L1 Instruction 128 KiB L2 Unified 4096 KiB (x8)Load Average: 4.69, 5.02, 4.78-------------------------------------------------------------------Benchmark Time CPU Iterations-------------------------------------------------------------------BENCHMARK_game 2914 ns 2913 ns 242382BENCHMARK_game_100 34.4 ns 34.3 ns 20322076BENCHMARK_game_1_000_000 61.6 ns 61.6 ns 11346874Oh, wow! Unser BENCHMARK_game Benchmark ist zurück, ungefähr dort, wo er bei dem ursprünglichen FizzBuzz war.
Ich wünschte, ich könnte mich genau an das Ergebnis erinnern. Es ist allerdings schon drei Wochen her.
Mein Terminalverlauf reicht nicht so weit zurück, und Google Benchmark speichert seine Ergebnisse nicht.
Aber ich denke, es ist nah dran!
Der BENCHMARK_game_100 Benchmark ist fast um das 50-fache gesunken auf 34.4 ns.
Und der BENCHMARK_game_1_000_000 Benchmark ist mehr als 1.500.000-fach gesunken! Von 110,879,642 ns auf 61.6 ns!
🐰 Hey, wenigstens haben wir diesen Performance-Fehler erwischt, bevor er in die Produktion gelangte… 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.
$ g++ -std=c++11 -isystem benchmark/include -Lbenchmark/build/src -lbenchmark -lpthread play_game.cpp benchmark_game.cpp -o benchmark_game$ bencher run --adapter cpp_google "./benchmark_game --benchmark_format=json"{ "context": { "date": "2023-10-16T16:00:00-04:00", "host_name": "bencher", "executable": "./benchmark_game", "num_cpus": 8, "mhz_per_cpu": 24, "cpu_scaling_enabled": false,...
View results:- BENCHMARK_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- BENCHMARK_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- BENCHMARK_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=trueMit 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
bencherCLI 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.