Wie man C++-Code mit Google Benchmark benchmarkt

Everett Pompeii

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 1 bis 100 (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 1 bis 100, und erhöhen Sie nach jeder Iteration.
  • Berechnen Sie für jede Zahl den Modulus (Rest nach der Division).
  • Wenn der Rest 0 ist, dann ist die Zahl ein Vielfaches des gegebenen Faktors:
    • Wenn der Rest bei 15 0 ist, dann drucken Sie FizzBuzz.
    • Wenn der Rest bei 3 0 ist, dann drucken Sie Fizz.
    • Wenn der Rest bei 5 0 ist, dann drucken Sie Buzz.
  • 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 && ./game
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
...
97
98
Fizz
Buzz

🐰 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:

play_game.cpp
#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 Zahl n entgegen und führt die eigentliche Fizz, Buzz, FizzBuzz oder Zahlenlogik durch, indem das Ergebnis als String zurückgegeben wird.
  • play_game: Nimmt eine ganze Zahl n entgegen, ruft fizz_buzz mit dieser Zahl auf und gibt das Ergebnis aus, wenn should_print auf true gesetzt ist.

Erstellen wir nun eine Header-Datei mit dem Namen play_game.h und fügen die Funktionsdeklaration von play_game hinzu:

play_game.h
#ifndef GAME_H
#define GAME_H
#include <string>
void play_game(int n, bool should_print);
#endif // GAME_H

Dann aktualisieren Sie die main-Funktion in game.cpp, um die Definition der play_game-Funktion aus der Header-Datei zu verwenden:

game.cpp
#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.git

Wechseln Sie in das neu geklonte Verzeichnis:

$ cd benchmark

Verwenden 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 Release

Zurück zum übergeordneten Verzeichnis gehen:

cd ..

Erstellen wir nun eine neue Datei namens benchmark_game.cpp:

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 zu benchmark::State nimmt.
  • Iterieren Sie über das benchmark::State-Objekt.
  • Für jede Iteration von 1 bis 100 (inklusive) iterieren.
    • Rufen Sie play_game mit der aktuellen Nummer und should_print auf false auf.
  • Übergeben Sie die Funktion BENCHMARK_game an den BENCHMARK Runner.
  • Führen Sie den Benchmark mit BENCHMARK_MAIN aus.

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_game
2023-10-16T14:00:00-04:00
Running ./benchmark_game
Run 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 1 bis 100 (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:

play_game.cpp
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 0 bis zu unserer gegebenen Zahl n einschließlich.
  • Initiieren Sie unsere Fibonacci-Sequenz beginnend mit 0 und 1 als die vorherige und aktuelle Zahl.
  • Iterieren Sie, während die aktuelle Zahl kleiner als die aktuelle Iteration i ist.
  • Addieren Sie die vorherige und aktuelle Zahl, um die nächste Zahl zu erhalten.
  • Aktualisieren Sie die vorherige Zahl zur aktuellen Zahl.
  • Aktualisieren Sie die aktuelle Zahl zur nächsten Zahl.
  • Sobald aktuell größer oder gleich der gegebenen Zahl n ist, verlassen wir die Schleife.
  • Prüfen Sie, ob die aktuelle Zahl gleich der gegebenen Zahl n ist, und wenn ja, geben Sie true zurück.
  • Andernfalls geben Sie false zurück.

Jetzt müssen wir unsere fizz_buzz Funktion aktualisieren:

play_game.cpp
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_buzz Funktion in fizz_buzz_fibonacci um, um sie aussagekräftiger zu machen.
  • Rufen Sie unsere is_fibonacci_number Hilfsfunktion auf.
  • Wenn das Ergebnis von is_fibonacci_number true ist, geben Sie Fibonacci zurück.
  • Wenn das Ergebnis von is_fibonacci_number false ist, führen Sie die gleiche Fizz, 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:

play_game.cpp
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_game
2023-10-16T15:00:00-04:00
Running ./benchmark_game
Run 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 12280

Wenn 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:

game.cpp
#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, um argc und argv zu 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 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 9
Fizz
$ ./game 10
Buzz
$ ./game 13
Fibonacci

Und wenn wir eine ungültige Zahl weglassen oder angeben:

$ ./game
Please, enter a positive integer to play...
$ ./game bad
Please, 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


SpongeBob Schwammkopf Drei Wochen später
Dies ist in Ordnung Meme

🐰 … 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:

benchmark_game.cpp
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_100 für das Spiel mit der Zahl hundert (100)
  • Ein Mikro-Benchmark BENCHMARK_game_1_000_000 fü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_game
2023-11-04T03:00:00-04:00
Running ./benchmark_game
Run 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 12560
BENCHMARK_game_100 1249 ns 1243 ns 564689

Warte… warte…

BENCHMARK_game_1_000_000 110879642 ns 43628118 ns 17

Was! 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:

play_game.cpp
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 🤦

play_game.cpp
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 0 und 1 als den jeweiligen previous und current Zahlen.
  • Iterieren Sie, während die current-Zahl kleiner ist als die gegebene Zahl n.
  • Fügen Sie die previous- und current-Zahl hinzu, um die next-Zahl zu erhalten.
  • Aktualisieren Sie die previous-Zahl auf die current-Zahl.
  • Aktualisieren Sie die current-Zahl auf die next-Zahl.
  • Sobald current größer oder gleich der gegebenen Zahl n ist, verlassen wir die Schleife.
  • Überprüfen Sie, ob die current-Zahl gleich der gegebenen Zahl n ist 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_game
2023-11-04T05:00:00-04:00
Running ./benchmark_game
Run 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 242382
BENCHMARK_game_100 34.4 ns 34.3 ns 20322076
BENCHMARK_game_1_000_000 61.6 ns 61.6 ns 11346874

Oh, 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=true

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

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.

🤖 Dieses Dokument wurde automatisch von OpenAI GPT-4 generiert. Es ist möglicherweise nicht korrekt und kann Fehler enthalten. Wenn Sie Fehler finden, öffnen Sie bitte ein Problem auf GitHub.