Pourquoi Rust est rapide, mais pas toujours ?
\nRust est un langage qui promet les performances du C++ avec la sécurité mémoire. Mais même en Rust, on peut écrire du code lent. Quelques mauvaises décisions et votre service commence à ralentir sous la charge. Bonne nouvelle : Rust vous donne tous les outils pour trouver et éliminer les goulots d'étranglement. Dans cet article, nous allons voir comment profiler du code Rust et quelles techniques d'optimisation fonctionnent vraiment.
\n\nNous ne parlerons pas de manière abstraite. Vous verrez des exemples de code concrets, apprendrez à utiliser perf, flamegraph, valgrind et les benchmarks intégrés. Après avoir lu cet article, vous serez capable de réaliser vous-même un audit de performance de votre projet.
Outils pour le profilage Rust
\nAvant d'optimiser quoi que ce soit, il faut comprendre ce qui ralentit. Voici trois outils efficaces qui vous aideront à voir à l'intérieur du programme.
\n\n1. Benchmarks avec cargo bench
\nLa méthode standard pour mesurer les performances est d'écrire un benchmark. En Rust, on utilise la crate criterion ou le framework de test intégré. Exemple :
use criterion::{black_box, criterion_group, criterion_main, Criterion};\n\nfn fibonacci(n: u64) -> u64 {\n match n {\n 0 => 0,\n 1 => 1,\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(black_box(20))));\n}\n\ncriterion_group!(benches, bench_fib);\ncriterion_main!(benches);\n\n\nExécution : cargo bench. En sortie, vous obtenez le temps d'exécution, l'écart type et une comparaison avec les exécutions précédentes. C'est la base par laquelle commencer toute optimisation.
2. Profilage avec perf et flamegraph
\nLorsque vous devez savoir quelles fonctions consomment le processeur, utilisez perf. Collectez les données, puis visualisez-les sous forme de flamegraph :
# Collecte de données\nperf record -g ./target/release/my_app\n# Génération du flamegraph\nperf script | stackcollapse-perf.pl | flamegraph.pl > profile.svg\n\n\nLe flamegraph vous montrera les zones « chaudes » du code. Plus un bloc est large, plus le temps est passé dans cette fonction. C'est la manière la plus visuelle de trouver un goulot d'étranglement.
\n\n3. Recherche de fuites avec Valgrind
\nRust protège contre les fuites mémoire au niveau de la compilation, mais pas contre toutes. Par exemple, si vous utilisez Rc ou Arc avec des références circulaires, la mémoire peut ne pas être libérée. Valgrind vous aidera à le détecter :
valgrind --tool=massif ./target/debug/my_app\n\n\nMassif montrera combien de mémoire le programme consomme à chaque instant. Si le graphique ne descend pas après la fin de l'exécution, cherchez une fuite.
\n\nTechniques pratiques d'optimisation de code en Rust
\nMaintenant que vous savez quoi chercher, voyons comment le corriger. Voici cinq méthodes éprouvées qui accélèrent réellement le code.
\n\n1. Abstractions à coût zéro : utilisez les itérateurs correctement
\nLes itérateurs en Rust sont compilés en un code aussi efficace que des boucles manuelles. Mais seulement si vous ne faites pas de copies inutiles. Comparez :
\n\n// Mauvais : on crée un Vec, on copie, on filtre\nlet result: Vec<u32> = data.iter().filter(|x| x % 2 == 0).collect();\n// Bon : on travaille avec l'itérateur sans collections intermédiaires\nlet sum: u32 = data.iter().filter(|x| x % 2 == 0).sum();\n\n\nLa deuxième option n'alloue pas de mémoire pour un vecteur temporaire. Si vous n'avez besoin que d'une seule valeur, ne collectionnez pas.
\n\n2. Mise en cache des résultats
\nSi une fonction est appelée plusieurs fois avec les mêmes arguments, mettez le résultat en cache. Utilisez HashMap ou la crate lru pour limiter la taille du cache :
use std::collections::HashMap;\n\nfn expensive_calc(x: u64) -> u64 {\n // ... calculs longs\n x * x\n}\n\nfn c