Wie misst man x86-64-Performanceoptimierung?
uOps, Abhängigkeiten, Caches und Sprungvorhersage mit Profiling und Benchmarks prüfen
Wie misst man x86-64-Performanceoptimierung?
Dieser Artikel ist als Lehrmaterial gedacht. Performance hängt von Mikroarchitektur, Energie- und Temperaturzustand, Speicherhierarchie, OS und Eingaben ab. Feste Cyclezahlen sind keine universelle Regel.
Weniger Befehle garantieren keinen schnelleren Code. Ein Befehl kann mehrere uOps ergeben, unabhängige Befehle können überlappen, und ein Cache Miss oder eine lange Abhängigkeit kann den gesamten Loop bestimmen. Optimierung bedeutet: Bottleneck messen, Kausalhypothese bilden und in kontrollierten Experimenten zu widerlegen versuchen.
Latenz und Durchsatz
Latenz ist die Zeit bis ein abhängiger Befehl ein Ergebnis nutzen kann; Durchsatz ist die Startfrequenz unabhängiger Operationen.
; dependency chain
add rax, rbx
add rax, rcx
add rax, rdx
; independent
add r8, rbx
add r9, rcx
add r10, rdx
Die gleiche Anzahl add hat verschiedene kritische Pfade. Ein Benchmark muss sagen, was er misst.
Mehrere Bottleneck-Ebenen
| Ebene | Beispiele |
|---|---|
| Front-end | Instruction Cache, Decode, uOp Cache |
| Execution | Ports, Latenz, Dependencies, Divider |
| Memory | Cache/DRAM/TLB, Store Forwarding |
| Control | Branch Misprediction, indirekte Ziele |
| System | Page Fault, Scheduler, Locks, I/O |
Quellzeilen, Befehlsanzahl, Binärbytes und jmp-Anzahl sind Hinweise, keine Zeitmessung. Erst mit Wall Time und Profiler den Hot Path bestätigen.
Lokalität und Zweigverteilung
Sequentielle Arrays nutzen Cachelines und Prefetching. Ein Feld großer Strukturen oder Pointer Chasing verschwendet Bandbreite und begrenzt Memory-Level Parallelism. AoS→SoA oder ein kleineres Working Set kann mehr bewirken als ASM.
Ein fast immer gleich gerichteter Sprung ist gut vorhersagbar. Unregelmäßige Daten können cmov oder Masken begünstigen, aber auch beide Pfade ausführen und Dependencies erhöhen. Künstlich zufällige Benchmarks können den Produktworkload falsch darstellen.
Benchmarkvertrag
Hypothese, Eingabegrößen und Verteilung, Schutz vor Dead-Code-Elimination, Setupanteil, Cold/Warm Cache und Unabhängigkeit der Wiederholungen dokumentieren. Außerdem CPU, Microcode, Compiler/Flags, ISA, LTO/PGO, Seed, Affinity, Energie, Temperatur, Wiederholungen, Ergebnisverteilung und OS-Last erfassen. Mehrere unabhängige Runs statt eines Bestwerts bewerten.
rdtsc ist nicht automatisch ein Funktionstimer; Out-of-order, Serialisierung, Migration und invariant TSC müssen behandelt werden. Zuerst ein vertrauenswürdiges Framework und monotone Uhr verwenden.
Profiler, Counter und Compiler
Sampling-Profiler finden die Zeitstelle; Hardwarecounter prüfen Hypothesen zu Cycles, Branches, Cache und TLB. Eventbedeutungen variieren nach CPU und enthalten Multiplexing, Skid und Spekulation. IPC allein ist kein Urteil.
Reihenfolge: korrekte getestete C/C++-Referenz, Release-Output, Algorithmus/Data Layout, PGO/Target/Intrinsics und erst dann ASM in engem Hot Loop. Separate ASM-Funktionen können Inlining und interprozedurale Optimierung verlieren.
Vor dem Zeitvergleich: 0, 1, Vektorgrenzen, große Eingaben, Overflow, Alignment, Alias/Overlap, FP-Sonderwerte, Feature-Fallback, Sanitizer und Unwind testen.
Zusammenfassung
x86-64-Performance kombiniert Latenz, Durchsatz, uOps, Ressourcen, Dependencies, Cache/TLB, Sprungvorhersage und Systemrauschen. Hot Path profilieren, eine genaue Hypothese benchmarken und handgeschriebenes ASM nur behalten, wenn Nutzen und Wartungsbedingungen reproduzierbar sind.