Profilage du code Rust : trouver les goulots d'étranglement en pratique
\n\nRust est réputé pour ses performances, mais même le code le plus rapide peut contenir des goulots d'étranglement inattendus. Le profilage est l'art et la science de l'identification de ces problèmes. Dans cet article, nous allons voir en pratique comment trouver et éliminer les « goulots d'étranglement » dans les applications Rust, en utilisant des outils et techniques modernes.
\n\nPourquoi le profilage est-il important pour Rust ?
\n\nBien que Rust garantisse l'absence de ramasse-miettes et offre un contrôle de bas niveau sur la mémoire, cela ne protège pas contre les algorithmes inefficaces, les allocations superflues ou une mauvaise utilisation du cache. Sans profilage, vous risquez d'optimiser ce qui ne doit pas l'être. Le profilage répond aux questions suivantes :
\n\n- \n
- Quelles fonctions consomment le plus de CPU ? \n
- Où se produisent les allocations mémoire fréquentes ? \n
- Quelles parties du code bloquent l'exécution (par exemple, à cause de mutex) ? \n
- Dans quelle mesure le cache du processeur est-il utilisé efficacement ? \n
Outils pour le profilage Rust
\n\nExaminons trois approches principales qui couvrent 90 % des tâches de profilage.
\n\n1. Benchmarking intégré avec cargo bench
\n\nLa méthode la plus simple consiste à utiliser cargo bench de l'écosystème Rust. Il permet de mesurer le temps d'exécution de fonctions individuelles.
Exemple de benchmark pour une fonction triant un vecteur :
\n\n// benches/my_bench.rs\nuse criterion::{black_box, criterion_group, criterion_main, Criterion};\n\nfn sort_benchmark(c: &mut Criterion) {\n let mut data: Vec<i32> = (0..10000).rev().collect();\n\n c.bench_function("sort 10k", |b| {\n b.iter(|| {\n // black_box empêche l'optimisation du compilateur\n let mut clone = black_box(data.clone());\n clone.sort();\n black_box(clone)\n })\n });\n}\n\ncriterion_group!(benches, sort_benchmark);\ncriterion_main!(benches);\n\nExécution : cargo bench
Vous obtiendrez un rapport avec le temps, l'écart type et la comparaison avec les exécutions précédentes. C'est idéal pour les tests de régression de performance.
\n\n2. Profileur système perf (Linux)
\n\nperf est un outil puissant du noyau Linux. Il collecte des échantillons de compteurs de performance (cycles CPU, défauts de cache, instructions) sans modification du code.
Utilisation de base :
\n\n# Collecte de données\nperf record -F 99 --call-graph dwarf ./my_rust_program\n\n# Visualisation du rapport\nperf report --stdio\n\n# Génération d'un flamegraph\nperf script | stackcollapse-perf.pl | flamegraph.pl > flame.svg\n\nLe drapeau -F 99 définit la fréquence d'échantillonnage à 99 Hz (généralement suffisant). --call-graph dwarf active l'enregistrement de la pile d'appels. En sortie, vous obtenez un flamegraph interactif (SVG), où l'axe X représente le temps et l'axe Y la pile d'appels. La largeur du rectangle indique le temps passé dans la fonction.
3. L'outil flamegraph pour Rust
\n\nIl existe un utilitaire spécialisé cargo-flamegraph qui simplifie la création de flamegraphs pour les projets Rust :
cargo install flamegraph\ncargo flamegraph --bin my_binary\n\nAprès exécution, un fichier flamegraph.svg apparaît à la racine du projet. Ouvrez-le dans un navigateur — vous verrez immédiatement les parties « chaudes » du code.
Exemple pratique : optimisation d'un analyseur syntaxique
\n\nImaginons que nous écrivions un analyseur JSON en Rust, et qu'il soit lent. Utilisons le profilage pour trouver le problème.
\n\nÉtape 1 : Benchmark
\n\nCréons un benchmark pour analyser un gros fichier JSON (par exemple, 10 Mo). Exécutons cargo bench et voyons que l'analyse prend 2,5 secondes. C'est beaucoup.
Étape 2 : Flamegraph
\n\nLançons cargo flamegraph sur le binaire qui analyse ce fichier. Ouvrons le SVG et voyons que 60 % du temps