Benchmarking und Profiling: Genaue Messungen der Code-Performance
Code-Optimierung ohne objektive Daten führt zu Fehlern. Eine umgeschriebene Hash-Funktion, die angeblich die Cache-Lokalität verbessern sollte, verlangsamte die Ausführung um 15 %. Die Intuition ist trügerisch – präzise Messungen von Zeit und Prozessorereignissen sind erforderlich.
Ein Benchmarking-Framework löst das Problem: Mehrfache Durchläufe, statistische Analyse, Integration mit perf. Damit lassen sich echte Engpässe identifizieren.
Hochpräzise Timing-Methoden
Die Standardfunktion time() bietet eine Auflösung von 1 Sekunde – unakzeptabel für Mikro-Optimierungen.
clock_gettime()
struct timespec start, end;
clock_gettime(CLOCK_MONOTONIC, &start);
run_test();
clock_gettime(CLOCK_MONOTONIC, &end);
long ns = (end.tv_sec - start.tv_sec) * 1000000000L +
(end.tv_nsec - start.tv_nsec);
Vorteile: Nanosekunden-Auflösung, resistent gegen Systemzeit-Anpassungen, POSIX-Portabilität.
CPU Cycle Counters (empfohlen)
RISC-V:
static inline uint64_t rdcycle(void) {
uint64_t cycles;
asm volatile ("rdcycle %0" : "=r" (cycles));
return cycles;
}
x86_64:
static inline uint64_t rdtsc(void) {
uint32_t lo, hi;
asm volatile ("rdtsc" : "=a" (lo), "=d" (hi));
return ((uint64_t)hi << 32) | lo;
}
ARM64:
static inline uint64_t rdcycle(void) {
uint64_t val;
asm volatile("mrs %0, pmccntr_el0" : "=r"(val));
return val;
}
Auflösung – 1 Zyklus, kein Overhead durch Systemaufrufe. Nachteile: architekturspezifisch, beeinflusst durch Frequenzänderungen.
Statistische Auswertung der Ergebnisse
Ein einzelner Durchlauf ist nutzlos wegen Cache-Variationen, OS-Unterbrechungen, Branching.
Basisstatistiken
#define ITERATIONS 1000
uint64_t times[ITERATIONS];
// ... fill times ...
uint64_t min = times[0], max = times[0], sum = 0;
for (int i = 0; i < ITERATIONS; i++) {
if (times[i] < min) min = times[i];
if (times[i] > max) max = times[i];
sum += times[i];
}
uint64_t mean = sum / ITERATIONS;
Erweiterte Statistiken
Der Median ist robust gegenüber Ausreißern, die Standardabweichung zeigt die Stabilität:
qsort(times, ITERATIONS, sizeof(uint64_t), compare_uint64);
uint64_t median = times[ITERATIONS / 2];
double variance = 0;
for (int i = 0; i < ITERATIONS; i++) {
double diff = (double)times[i] - (double)mean;
variance += diff * diff;
}
double stddev = sqrt(variance / ITERATIONS);
Wichtige Metriken:
- Minimum: Bestfall (warmer Cache)
- Median: typische Leistung
- Stddev: Variabilität
- Maximum: schlechtester Fall
Benchmarking-Framework
Universelle Schnittstelle für die Wiederverwendung:
typedef struct {
const char *name;
void (*setup)(void);
void (*run)(void);
void (*teardown)(void);
} benchmark_t;
void benchmark_run(benchmark_t *bench, int iterations);
Implementierung umfasst:
- Aufwärmdurchlauf
- Mehrfache Messungen
- Statistische Auswertung
Beispiel:
int array[1000];
void setup_array(void) {
for (int i = 0; i < 1000; i++) {
array[i] = i;
}
}
void test_sequential_access(void) {
volatile int sum = 0;
for (int i = 0; i < 1000; i++) {
sum += array[i];
}
}
benchmark_t bench = {
.name = "Sequential Array Access",
.setup = setup_array,
.run = test_sequential_access,
.teardown = NULL
};
benchmark_run(&bench, 1000);
Cache-Analyse mit perf
perf stat erfasst Hardware-Zähler:
$ perf stat -e cache-references,cache-misses ./program
1,234,567 cache-references
12,345 cache-misses # 1.00% miss rate
Nützliche Events:
cache-references/misses: alle EbenenL1-dcache-loads/misses: L1-DatenLLC-loads/misses: letzte Ebene
Strukturvergleich: Array – 1,2K Misses, verkettete Liste – 45K Misses (37x schlechter).
Integration von perf ins Framework
typedef struct {
uint64_t cycles;
uint64_t cache_references;
uint64_t cache_misses;
uint64_t l1_loads;
uint64_t l1_misses;
} perf_counters_t;
Automatische Erfassung und Aggregation von perf_event_open-Zählern.
Häufige Probleme und Lösungen
Compiler-Optimierungen
// Bad: loop eliminated
int sum = 0;
for (...) sum += array[i];
// Good
volatile int sum = 0;
Kalter vs. warmer Cache
Erster Durchlauf ist langsamer. Lösung: Aufwärmen + separate Metrik.
Messoverhead
rdcycle() benötigt ~10 Zyklen. Abziehen oder lange Tests verwenden.
Systemrauschen
- Viele Iterationen
- Median statt Mittelwert
cpupower frequency-set -g performancetaskset -c 0nice -n -20
Was zählt
- CPU-Zähler: rdtsc/rdcycle – Goldstandard für Genauigkeit
- Statistiken: Median + Stddev zuverlässiger als Mittelwert
- perf-Events: Cache-Misses enthüllen Lokalitätsprobleme
- Cache-Aufwärmen: essenziell für realistische Messungen
- Volatile: verhindert Dead-Code-Optimierung
— Editorial Team
Noch keine Kommentare.