- Ein Open-Source-Projekt, mit dem sich High-Performance-C/C++- und Assembler-Coding-Techniken anhand praxisnaher Beispiele lernen lassen
- Enthält Beispiele für den Einsatz optimierter Bibliotheken statt STL sowie verschiedener Hardware-Optimierungstechniken
- Erklärt zahlreiche Performance-Tricks wie Kosten der Eingabegenerierung, Approximation mathematischer Funktionen, CPU-Branch-Prediction und Multicore-Parallelisierung
- Behandelt umfassend plattformbezogene Optimierungstechniken für CUDA, PTX, ASM, FPGA, JSON-Verarbeitung usw. sowie Methoden zur Benchmark-Messung
- Bietet auf Basis von Google Benchmark Funktionen zur Automatisierung von Benchmark-Ausführung und statistischer Auswertung
So schreibt man performanceorientierten C/C++- und Assembler-Code
- Dieses Projekt ist eine Sammlung von Benchmark-Code, die dabei hilft, die für das Design von Hochleistungssoftware nötige Intuition und Denkweise zu entwickeln
- Es behandelt praxisnahe Codebeispiele, um typische Bugs, Sicherheitsprobleme und Performance-Engpässe in modernem Code zu vermeiden
- Es stellt systematisch performanceorientierte Techniken aus der Praxis vor, die in Uni-Vorlesungen oder Bootcamps kaum vermittelt werden
- Der Großteil des Codes läuft in Linux-Umgebungen auf Basis von GCC und Clang, teilweise werden aber auch Windows und macOS unterstützt
- Vorgestellt werden außerdem parallele Algorithmen, Coroutines und Polymorphismus für die Implementierung performanter Software
Wichtige Themen
- Zufällige Eingaben zum hundertfach niedrigeren Preis?! Die Eingabegenerierung kann langsamer sein als der Algorithmus selbst
- 1 % Fehler bei nur 1/40 der Kosten: STL-Trigonometriefunktionen wie
std::sin in nur drei Zeilen Code approximieren
- Lazy-Logik ist viermal schneller? Extreme Faulheit mit benutzerdefinierten
std::ranges und Iteratoren umsetzen
- Compiler-Optimierung über
-O3 hinaus: Mit versteckten Flags und Tricks lässt sich die Performance noch einmal verdoppeln
- Matrizenmultiplikation ist das Problem? 3x3x3-GEMM kann trotz 60 % weniger Operationen 70 % langsamer sein als 4x4x4
- Die Wahrheit über AI-Scaling? Die Lücke zwischen theoretischem ALU-Durchsatz und realer BLAS-Performance messen
- Wie viele
ifs sind zu viele? Mit nur zehn Zeilen Code die Grenzen des CPU-Branch-Predictors testen
- Ist Rekursion wirklich besser? Die Stack-Tiefe selbst messen und sehen, wo
SEGFAULT auftritt
- Warum Ausnahmen vermieden werden sollten? Alternativen wie
std::error_code oder std::variant ausprobieren
- Wie skaliert man auf Multicore? Mit OpenMP, Intel oneTBB oder einem selbstgebauten Thread-Pool
- Wie verarbeitet man JSON ohne Speicherallokation? Ist C++20 besser, oder sind klassische C99-Tools einfacher?
- STL-assoziative Container richtig nutzen: Wie verwendet man benutzerdefinierte Schlüssel und transparente Vergleichsoperatoren?
- Gibt es etwas Schnelleres als einen handgeschriebenen Parser? Direktvergleich mit einer auf
consteval basierenden Regex-Engine
- Sind Pointer wirklich 64 Bit groß? Pointer-Tagging in der Praxis nutzen
- Wie viele Pakete verwirft UDP? Bis hin zur Verarbeitung von Web-Requests aus dem User Space mit
io_uring
- Scatter-Gather für 50 % schnellere vektorisierte Operationen auf nicht zusammenhängendem Speicher
- Intel oneAPI vs. Nvidia CCCL? Was ist besonders an
<thrust> und <cub>?
- CUDA C++, PTX, SASS: Worin unterscheiden sie sich von CPU-Code?
- Wenn der Code performancekritisch ist: Vergleich von Intrinsics, Inline-
asm und .S-Dateien
- Tensor Cores und Speicherarchitektur — wie unterscheiden sich CPU sowie Volta-, Ampere-, Hopper- und Blackwell-GPUs?
- Wie unterscheidet sich FPGA-Coding von GPU-Programmierung? Was sind die Unterschiede zwischen High-Level Synthesis (HLS), Verilog und VHDL? 🔜 #36
- Was ist eine Encrypted Enclave? Latenzvergleich von Intel SGX, AMD SEV und ARM Realm 🔜 #31
Ausführung und Einrichtung
- Linux + GCC empfohlen, auch WSL oder Clang auf dem Mac (nicht die Standard-Distribution) sind nutzbar
- Erforderlich sind
CMake, liburing, OpenBLAS, g++, build-essential
- Nach dem Build der ausführbaren Datei
less_slow werden die Benchmarks beim Start automatisch ausgeführt
git clone https://github.com/ashvardanian/less_slow.cpp.git
cd less_slow.cpp
pip install cmake --upgrade
sudo apt install -y build-essential g++ liburing-dev libopenblas-base
cmake -B build_release -D CMAKE_BUILD_TYPE=Release
cmake --build build_release --config Release
build_release/less_slow
- Ob CUDA und Intel TBB verwendet werden, ist optional (
-D USE_INTEL_TBB=OFF usw.)
- Beim Ausführen lassen sich einzelne Benchmarks auswählen, Ergebnisse als JSON speichern und Ausgabeformate festlegen
build_release/less_slow --benchmark_filter=std_sort
build_release/less_slow --benchmark_out=results.json --benchmark_format=json
Tipps für bessere Performance-Messungen
- SMT deaktivieren und random interleaving verwenden, um Rauschen zu minimieren
- Mit der Option
--benchmark_perf_counters von Google Benchmark lassen sich Hardware-Performance-Counter messen
sudo build_release/less_slow --benchmark_perf_counters="CYCLES,INSTRUCTIONS"
- Alternativ kann der Benchmark auch mit dem Linux-Tool
perf gemessen werden
sudo perf stat taskset 0xEFFFEFFFEFFFEFFFEFFFEFFFEFFFEFFF build_release/less_slow --benchmark_filter=super_sort
Projekt-Dateistruktur
- Hauptquelle:
less_slow.cpp (Schwerpunkt auf CPU-Benchmark-Code)
- Enthält plattformspezifische Optimierungsdateien: ASM für x86/ARM, CUDA-
.cu und PTX-.ptx-Code
├── less_slow.cpp # Haupt-Benchmark-Code
├── less_slow_amd64.S # x86-Assembler
├── less_slow_aarch64.S # ARM-Assembler
├── less_slow.cu # CUDA C++
├── less_slow_sm70.ptx # PTX IR (Volta)
├── less_slow_sm90a.ptx # PTX IR (Hopper)
Verwendung externer Bibliotheken
- Google Benchmark: Performance-Messung
- Intel oneTBB: Paralleles STL-Backend
- Meta libunifex: Asynchrones Ausführungsmodell
- range-v3: Ersatz für
std::ranges
- fmt: Ersatz für
std::format
- StringZilla: Ersatz für
std::string
- CTRE: Ersatz für
std::regex
- nlohmann/json, yyjson: JSON-Parser
- Abseil: High-Performance-Container
- cppcoro: Coroutine-Implementierung
- liburing: Linux-Kernel-umgehendes I/O
- ASIO: Asynchrones Networking
- Nvidia CCCL, CUTLASS: GPU-Algorithmen und Matrixoperationen
Kurzüberblick: Tipps zur Nutzung von Google Benchmark
- Benchmarks mit
BENCHMARK() registrieren, Parameter mit ->Args({x,y}) übergeben
- Compiler-Optimierung mit
DoNotOptimize() und ClobberMemory() steuern
- Anzahl der Wiederholungen und Benchmark-Dauer mit
->Iterations(n) und ->MinTime(n) festlegen
- Zeitkomplexität mit
->Complexity(...) und ->SetComplexityN(n) angeben
- Timing-Bereiche mit
state.PauseTiming() und ResumeTiming() direkt steuern
- Benutzerdefinierte Counter lassen sich über
state.counters[...] registrieren
Meme- und Humor-Elemente
- In das Lehrmaterial sind technische Meme-Bilder eingebaut, um das Interesse zu steigern
- Der Konflikt zwischen Performance und Abstraktion sowie IEEE-754-Gleitkommazahlen werden humorvoll dargestellt
1 Kommentare
Hacker-News-Kommentare
40-mal schnellere Trigonometrie: Standardbibliotheksfunktionen wie
std::sinlassen sich mit 3 Zeilen Code beschleunigensin(x)annähern, indem man die Entwicklung auf einige wenige Terme begrenztErfahrung aus der Arbeit mit Mikrocontrollern
Meinung zu den Entscheidungen von Abseil
Kritik an Verrenkungen für Performance in C++
Unterschiede zwischen FPGA- und GPU-Programmierung sowie der Wunsch nach Inhalten zu High-Level-Synthese, Verilog und VHDL
Neue Informationen über denormalisierte Gleitkommazahlen
Positives Feedback zum Beitrag über Google Benchmark
Erwartungen an „weniger langsamen C-, C++- und Assembler-Code“