Warum erfordert Rust einen besonderen Ansatz beim Benchmarking?
Rust ist für seine Geschwindigkeit bekannt, aber selbst die schnellste Sprache kann durch eine ungünstige Implementierung verlangsamt werden. Haben Sie jemals einen Algorithmus in Rust geschrieben und gedacht: "Das wird blitzschnell sein!", ihn dann ausgeführt und nicht verstanden – wo die Mikrosekunden verloren gegangen sind? Kommt Ihnen bekannt vor?
Benchmarking in Rust ist nicht einfach nur das Starten eines Timers und das Messen der Zeit. Es ist eine ganze Wissenschaft: Man muss Compiler-Optimierungen, CPU-Caching und sogar die Art und Weise berücksichtigen, wie Rust den Speicher verwaltet. In diesem Artikel werden wir uns ansehen, wie man Programme in Rust richtig benchmarkt, damit Sie ehrliche Zahlen erhalten und keine zufälligen Ergebnisse.
Wir werden den Weg von einfachen Messungen mit std::time bis zum professionellen Werkzeug criterion gehen, das in echten Projekten verwendet wird. Los geht's!
1. Der einfachste Weg: std::time::Instant
Wenn Sie schnell die Leistung eines Codeabschnitts "abtasten" möchten – ohne Installation von Crates – fügen Sie die Messung einfach direkt in main ein. Aber es gibt einen Haken: Der Rust-Optimierer kann Ihren Code "wegwerfen", wenn er das Ergebnis nicht verwendet.
use std::time::Instant;
fn main() { let start = Instant::now();
// Ihr Code, der gemessen werden soll let mut sum = 0u64; for i in 0..1_000_000 { sum += i; }
let duration = start.elapsed(); println!("Ausführungszeit: {:?}", duration); // Verwenden Sie das Ergebnis unbedingt! println!("Summe: {}", sum);}Warum kann man nicht einfach einen einzelnen Durchlauf messen?
Ein einzelner Durchlauf ist eine Lotterie. Das System könnte ausgelastet sein, die CPU drosselt, der Cache ist kalt. Ein korrektes Benchmark in Rust sollte:
- Den Code aufwärmen (warm-up) – mehrere Iterationen vor der Messung
- Den Code viele Male ausführen (tausende Iterationen)
- Die Ergebnisse mitteln
Die manuelle Implementierung davon ist ein Weg zu Fehlern. Deshalb hat die Rust-Community criterion entwickelt.
2. Professionelles Benchmarking: Die Crate Criterion
Criterion.rs ist der De-facto-Standard für Benchmarking in Rust. Es wird im Compiler rustc selbst und in Hunderten von Crates verwendet. Warum ist es so gut?
- Automatisches Aufwärmen und statistische Verarbeitung
- Grafiken und Vergleich mit vorherigen Durchläufen
- Erkennung von Regressionen (wenn der Code langsamer geworden ist, erfahren Sie es)
- Unterstützung für parametrisierte Benchmarks
Installation und Einrichtung
Fügen Sie in Cargo.toml hinzu:
[dev-dependencies]criterion = { version = "0.5", features = ["html_reports"] }
[[bench]]name = "my_benchmark"harness = falseErstellen Sie die Datei benches/my_benchmark.rs:
use criterion::{black_box, criterion_group, criterion_main, Criterion};
// Funktion, die gebenchmarkt wirdfn fibonacci(n: u64) -> u64 { match n { 0 => 0, 1 => 1, _ => fibonacci(n - 1) + fibonacci(n - 2), }}
fn bench_fibonacci(c: &mut Criterion) { c.bench_function("fibonacci 20", |b| { // black_box – verhindert, dass der Optimierer den Code wegwirft b.iter(|| fibonacci(black_box(20))) });}
criterion_group!(benches, bench_fibonacci);criterion_main!(benches);Ausführung: cargo bench. Sie werden etwas sehen wie:
fibonacci 20 time: [24.567 µs 24.789 µs 25.012 µs]Die drei Zahlen sind das Konfidenzintervall (untere, mittlere, obere Grenze). Wenn das Intervall eng ist, ist das Benchmark stabil.
3. Die geheime Zutat: black_box
Sie haben vielleicht black_box im Beispiel bemerkt. Dies ist eine magische Funktion aus criterion, die LLVM verbietet, die Argumente oder das Ergebnis zu optimieren. Ohne sie könnte Ihr Benchmark 0 ns anzeigen – weil der Compiler die Konstante einfach zur Compile-Zeit berechnet hat.
Beispiel p