Introduction : pourquoi Rust est déjà rapide, mais ce n'est pas une limite
\n\nRust est à juste titre considéré comme l'un des langages de programmation système les plus performants. Grâce aux abstractions à coût zéro (zero-cost abstractions) et à un système de propriété strict, le code Rust est souvent aussi rapide que le code équivalent en C ou C++. Cependant, même en Rust, il existe des « pièges » qui peuvent ralentir une application, en particulier chez les développeurs débutants.
\n\nDans cet article, nous allons analyser les principes clés et les techniques pratiques d'optimisation des applications Rust. Vous apprendrez à profiler le code, à éviter les allocations inutiles, à travailler efficacement avec les collections et à utiliser la puissance du compilateur. Le contenu est destiné aux débutants, donc tous les exemples seront aussi simples et compréhensibles que possible.
\n\nPrêt à rendre votre code Rust encore plus rapide ? C'est parti !
\n\n1. Profilage : d'abord mesurer, ensuite optimiser
\n\nAvant d'optimiser quoi que ce soit, il faut comprendre ce qui ralentit. En Rust, il existe d'excellents outils pour cela.
\n\n1.1. Benchmarks avec cargo bench
\n\nLa méthode standard consiste à utiliser des benchmarks. Ils permettent de mesurer le temps d'exécution de fonctions spécifiques.
\n\n// Dans le fichier 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\nPour lancer, ajoutez dans Cargo.toml :
[dev-dependencies]\ncriterion = "0.5"\n\n[[bench]]\nname = "my_bench"\nharness = false\n\nEnsuite, exécutez cargo bench. Vous obtiendrez des chiffres précis : temps d'exécution, écart type et comparaison avec les exécutions précédentes.
1.2. Profilage CPU et mémoire
\n\nPour une analyse plus approfondie, utilisez :
\n- \n
- perf (Linux) — profileur au niveau du noyau. \n
- Valgrind — pour détecter les fuites mémoire et analyser le cache. \n
- flamegraph — visualisation des « points chauds » du code. \n
Exemple de génération de flamegraph :
\ncargo install flamegraph\ncargo flamegraph --bin my_app\n# Ouvrir flamegraph.svg dans le navigateur\n\n\nLe flamegraph montrera quelles fonctions consomment le plus de temps. Cela vous aidera à vous concentrer sur l'essentiel.
\n\n2. Gestion de la mémoire : éviter les allocations inutiles
\n\nLes allocations sur le tas sont l'une des causes les plus fréquentes de code lent. Rust donne un contrôle total sur la mémoire, mais les débutants abusent souvent de Box, Vec et String.
2.1. Utilisez la pile plutôt que le tas
\n\nChaque fois que possible, stockez les données sur la pile. Par exemple, au lieu d'un Vec pour un petit ensemble fixe, utilisez un tableau :
// Lent : allocation sur le tas\nlet data: Vec<i32> = vec![1, 2, 3, 4, 5];\n\n// Rapide : pile\nlet data: [i32; 5] = [1, 2, 3, 4, 5];\n\n\n2.2. Allocation préalable de mémoire
\n\nSi la taille de la collection est connue à l'avance, utilisez with_capacity :
// Mauvais : beaucoup de réallocations\nlet mut v = Vec::new();\nfor i in 0..1000 {\n v.push(i);\n}\n\n// Bon : une seule allocation\nlet mut v = Vec::with_capacity(1000);\nfor i in 0..1000 {\n v.push(i);\n}\n\n\n2.3. Évitez le clonage
\n\nCloner des chaînes et des structures volumineuses est une opération coûteuse. Utilisez l'emprunt (&str au lieu de String) :
fn process(s: &str) { /* ... */ }\n\n// Mauvais : clonage\nlet s = String::from("hello");\nprocess(&s.clone());\n\n// Bon : emprunt\nprocess(&s);\n\n\n