Einleitung: Warum Rust bereits schnell ist, aber das nicht die Grenze ist
\n\nRust gilt zu Recht als eine der leistungsfähigsten systemnahen Programmiersprachen. Dank der Zero-Cost-Abstractions und des strengen Eigentümersystems läuft Rust-Code oft genauso schnell wie äquivalenter C- oder C++-Code. Allerdings gibt es auch in Rust "Fallstricke", die eine Anwendung verlangsamen können, insbesondere bei Anfängern.
\n\nIn diesem Artikel werden wir die wichtigsten Prinzipien und praktischen Techniken zur Optimierung von Rust-Anwendungen erläutern. Sie erfahren, wie Sie Code profilieren, unnötige Allokationen vermeiden, effizient mit Collections arbeiten und die Leistungsfähigkeit des Compilers nutzen. Das Material richtet sich an Anfänger, daher werden alle Beispiele so einfach und verständlich wie möglich sein.
\n\nBereit, Ihren Rust-Code noch schneller zu machen? Los geht's!
\n\n1. Profiling: Erst messen, dann optimieren
\n\nBevor Sie etwas optimieren, müssen Sie verstehen, was genau bremst. In Rust gibt es dafür hervorragende Werkzeuge.
\n\n1.1. Benchmarks mit cargo bench
\n\nDie Standardmethode ist die Verwendung von Benchmarks. Sie ermöglichen es, die Ausführungszeit bestimmter Funktionen zu messen.
\n\n// In der Datei benches/my_bench.rs\nuse criterion::{criterion_group, criterion_main, Criterion};\n\nfn fibonacci(n: u64) -> u64 {\n match n {\n 0 => 1,\n 1 => 1,\n n => fibonacci(n-1) + fibonacci(n-2),\n }\n}\n\nfn bench_fib(c: &mut Criterion) {\n c.bench_function("fib 20", |b| b.iter(|| fibonacci(20)));\n}\n\ncriterion_group!(benches, bench_fib);\ncriterion_main!(benches);\n\n\nFügen Sie zum Ausführen Folgendes in Cargo.toml hinzu:
[dev-dependencies]\ncriterion = "0.5"\n\n[[bench]]\nname = "my_bench"\nharness = false\n\nFühren Sie dann cargo bench aus. Sie erhalten genaue Zahlen: Ausführungszeit, Standardabweichung und einen Vergleich mit vorherigen Läufen.
1.2. CPU- und Speicher-Profiling
\n\nFür eine tiefergehende Analyse verwenden Sie:
\n- \n
- perf (Linux) — Profiler auf Kernel-Ebene. \n
- Valgrind — zum Auffinden von Speicherlecks und zur Cache-Analyse. \n
- flamegraph — Visualisierung von "Hot Spots" im Code. \n
Beispiel zur Generierung eines Flamegraphs:
\ncargo install flamegraph\ncargo flamegraph --bin my_app\n# Öffnen Sie flamegraph.svg im Browser\n\n\nDer Flamegraph zeigt, welche Funktionen die meiste Zeit verbrauchen. Das hilft, sich auf das Wesentliche zu konzentrieren.
\n\n2. Arbeit mit Speicher: unnötige Allokationen vermeiden
\n\nAllokationen auf dem Heap sind eine der häufigsten Ursachen für langsamen Code. Rust gibt Ihnen die volle Kontrolle über den Speicher, aber Anfänger neigen dazu, Box, Vec und String übermäßig zu nutzen.
2.1. Den Stack anstelle des Heaps verwenden
\n\nSpeichern Sie Daten wann immer möglich auf dem Stack. Verwenden Sie beispielsweise anstelle von Vec für einen kleinen, festen Satz ein Array:
// Langsam: Allokation auf dem Heap\nlet data: Vec<i32> = vec![1, 2, 3, 4, 5];\n\n// Schnell: Stack\nlet data: [i32; 5] = [1, 2, 3, 4, 5];\n\n\n2.2. Vorab-Speicherreservierung
\n\nWenn die Größe einer Collection im Voraus bekannt ist, verwenden Sie with_capacity:
// Schlecht: viele Reallokationen\nlet mut v = Vec::new();\nfor i in 0..1000 {\n v.push(i);\n}\n\n// Gut: eine Allokation\nlet mut v = Vec::with_capacity(1000);\nfor i in 0..1000 {\n v.push(i);\n}\n\n\n2.3. Klonen vermeiden
\n\nDas Klonen von Strings und großen Strukturen ist eine teure Operation. Verwenden Sie Ausleihen (&str anstelle von String):
fn process(s: &str) { /* ... */ }\n\n// Schlecht: Klonen\nlet s = String::from("hello");\nprocess(&s.clone());\n\n// Gut: Ausleihen\nprocess(&s);\n\n\n