Il y a une observation bien connue sur l'attente : sous dix secondes, vous restez concentré ; passé une minute, vous basculez le contexte et perdez le fil. Les temps de build s'assoient précisément dans cette gamme dangereuse. Une compilation de quatre-vingt-dix secondes ne coûte pas juste quatre-vingt-dix secondes — cela coûte le rechargement de tout ce que vous teniez dans votre tête quand vous revenez de l'onglet auquel vous avez basculé. Multipliez par cinquante builds par jour sur une équipe et les builds lents cessent d'être une gêne et commencent à modeler le comportement : commits plus grands et plus risqués parce que l'itération est chère, moins de refactorisation parce que la boucle de rétroaction le punit, et les tests sautés localement parce qu'ils prennent trop de temps.
La chose encourageante à propos de la performance des builds en 2026 est que les plus grands gains sont généralement la configuration, pas l'ingénierie. Vous n'avez pas à restructurer votre codebase ; vous installez quelques outils qui adressent les trois phases distinctes où le temps va réellement. Ce guide couvre ces phases — compilation, lien, et test — via sccache, mold, et cargo-nextest, plus bacon pour la boucle interne, et, important, comment mesurer si tout cela a aidé.
Mesurer avant d'optimiser
L'erreur la plus courante est d'optimiser la mauvaise phase. « Le build est lent » n'est pas un diagnostic — un build est au minimum la compilation, le lien, et (si vous les exécutez) les tests, et l'équilibre entre eux varie énormément par projet. Une codebase avec de nombreux petits crates et un énorme binaire final peut passer la plupart de son temps à lier ; une avec de généricité lourde et des macros peut être dominée par la compilation ; un projet mature peut passer plus de temps dans les tests que dans soit.
Alors commencez par une ventilation de timing plutôt qu'une supposition. En Rust, cargo build --timings produit un rapport HTML montrant exactement combien de temps chaque crate a pris et où le parallélisme s'est arrêté — révélant souvent qu'une dépendance sérialise tout le reste. Pour une vue plus grossière, time cargo build sur un arbre propre versus un incrément vous dit combien vous payez pour les builds froids. Et hyperfine vous donne des comparaisons avant/après statistiquement solides plutôt que la seule exécution bruyante qui est facile de vous tromper avec.
Cela est important parce que chaque outil ci-dessous adresse une phase et ne fait rien pour les autres. Installer un linker plus rapide quand votre goulot d'étranglement est la compilation produit une erreur d'arrondi et un faux sentiment de progrès.
Compilation : arrêtez de reconstruire ce que vous avez déjà construit
La source la plus courante de gaspillage en compilation est la redondance — construire le même crate de dépendance, avec les mêmes flags, que vous ou un collègue ou un coureur CI avez déjà construit il y a une heure. sccache adresse cela directement : il enveloppe le compilateur, fait un hash des entrées, et retourne un artefact mis en cache quand il a vu cette compilation exacte avant. Il supporte Rust, C/C++, et CUDA.
Ce qui élève sccache au-delà d'un cache local sont les backends de stockage partagés — S3, GCS, Redis, ou GitHub Actions cache. Cela change l'économie de CI en particulier. L'expérience CI par défaut est une machine froide compilant chaque dépendance à partir de zéro à chaque exécution, ce qui est du gaspillage pur puisque ces dépendances n'ont pas changé. Avec un cache partagé, la première exécution le peuple et chaque exécution suivante télécharge à la place de compiler. Les équipes voient couramment les temps de build CI chuter de moitié ou plus, et le même cache sert les machines des développeurs.
La configuration est une seule variable d'environnement (RUSTC_WRAPPER=sccache) ou une entrée ~/.cargo/config.toml. Le suivi important est la vérification : sccache --show-stats rapporte votre taux de hit, et un faible taux de hit est un signal que quelque chose dans vos entrées varie — RUSTFLAGS instable, les chemins absolus cuits dans la sortie, ou la compilation incrémentale qui interfère. Un cache avec un faible taux de hit est pire qu'aucun, parce que vous payez le coût de recherche pour rien.
Lien : la queue sérialisée
Le lien est la phase que les gens oublient, et c'est souvent le pire contrevenant dans la boucle édition-compilation-exécution. Voici pourquoi : la compilation se parallélise magnifiquement entre les cores, mais le lien traditionnellement ne le fait pas. Vous compilez deux cents fichiers sur seize cores en vingt secondes, puis attendez huit secondes tandis qu'un core lie. Dans une reconstruction incrémentale — où vous avez changé un seul fichier et seul ce fichier recompile — le lien peut être la plupart de votre attente.
mold est un linker conçu dès le départ pour utiliser tous les cores disponibles. Il routinement réduit les temps de lien d'un ordre de magnitude, et parce que la victoire tombe carrément dans le chemin de reconstruction incrémentale, c'est le changement que les développeurs sentent le plus immédiatement. L'adoption est genuinement triviale : mold -run cargo build enveloppe n'importe quelle commande de build sans configuration, ou vous ajoutez -fuse-ld=mold à vos flags de linker pour une configuration permanente.
La raison de combiner mold avec sccache est qu'ils attaquent les deux moitiés de la même attente. sccache élimine la compilation que vous avez faite avant ; mold accélère le lien qui reste et ne peut pas être mis en cache. Ni n'y a de substitut pour l'autre, et ensemble ils produisent généralement une plus grande amélioration que soit seul.
Test : isolation et défaillances honnêtes
La troisième phase est les tests, et ici cargo-nextest change le modèle plutôt que juste la vitesse. cargo test standard exécute tous les tests dans un binaire au sein d'un processus ; nextest exécute chaque test dans son propre processus. Cela apporte plusieurs conséquences au-delà du speedup typique de 2–3x.
L'isolation devient réelle. Les tests ne peuvent pas corrompre l'état global de l'autre, donc une défaillance dépendante de l'ordre se surface immédiatement au lieu d'apparaître mystérieusement des mois plus tard. Un crash est attribuable — si un test segfault ou aborte, vous apprenez lequel, plutôt que de perdre les résultats du binaire entier. Les tests flaky sont nommés comme tels : avec --retries, un test qui échoue puis passe est rapporté comme FLAKY plutôt que de passer silencieusement au rerun, ce qui est important parce qu'un test flaky est un problème différent d'un test échoué et le cacher c'est comment les suites se décomposent. Et le sharding CI est intégré via --partition, donc diviser une suite entre les coureurs est un flag plutôt qu'un projet de scripts.
La lacune principale à connaître : nextest n'exécute pas les doctests, donc le modèle CI courant est cargo nextest run && cargo test --doc.
La boucle interne : ne pas exécuter les builds manuellement
Le build le plus rapide est celui que vous n'aviez pas à invoquer. bacon s'exécute dans un terminal latéral, surveille votre source, et réexécute cargo check, clippy, ou tests à chaque sauvegarde, affichant un résumé toujours actualisé et compact des erreurs. Le gain n'est pas la vitesse brute — c'est que la compilation chevauche votre réflexion plutôt que de la bloquer, et vous voyez la première erreur en évidence plutôt que de faire défiler la sortie.
Cela s'associe naturellement avec les outils de phase : bacon donne une rétroaction continue, sccache et mold rend chacune de ces exécutions de fond assez rapide pour terminer avant que vous ayez fini de lire l'erreur précédente. Pour les projets non-Rust, watchexec fournit le même modèle de rétroaction continue pour n'importe quelle commande.
Les assembler, et vérifier
Une configuration complète est courte :
cargo install sccache cargo-nextest bacon --locked
sudo apt install mold # ou brew install mold
# ~/.cargo/config.toml
[build]
rustc-wrapper = "sccache"
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
Puis vérifier chaque pièce indépendamment, parce qu'une configuration silencieuse est facile : sccache --show-stats devrait afficher un taux de hit croissant ; readelf -p .comment ./target/debug/yourbin | grep -i mold confirme que mold l'a lié réellement ; cargo nextest run devrait visiblement terminer plus rapidement que cargo test. Mesurer le résultat end-to-end avec hyperfine sur un changement réaliste — toucher un fichier, reconstruire — plutôt que sur un build propre, puisque les reconstructions incrémentales sont ce que vous faites réellement toute la journée.
En CI, ajoutez le backend de cache partagé (SCCACHE_GHA_ENABLED=true sur GitHub Actions, ou un bucket S3) et utilisez le --partition de nextest pour sharding entre les coureurs. CI est où ces outils paient le plus dramatiquement, parce que les machines CI sont froides par défaut et répètent le même travail gaspillé à chaque exécution.
Au-delà de la configuration : ce qui rend réellement les builds lents
Si vous avez appliqué les outils de phase et la boucle est toujours pénible, les causes restantes sont structurales — et ça vaut le coup de comprendre même si vous décidez de ne pas agir, parce qu'elles expliquent pourquoi certains projets résistent à l'optimisation.
Le nombre et profondeur de dépendances est le plus courant. Chaque crate sur lequel vous dépendez doit être compilé au moins une fois, et un graphe de dépendances profond sérialise : le crate C ne peut pas commencer avant que B finisse, qui a attendu A. cargo build --timings le montre directement comme un long chemin critique avec les cores inactifs. L'audit pour les dépendances que vous utilisez trivialement — tirant un grand crate pour une seule fonction d'aide — est souvent le fix de plus haut effet de levier, et cela réduit aussi la surface de la chaîne d'approvisionnement en même temps.
Le code générique et riche en macros coûte le temps de compilation en proportion de l'instantiation. Une fonction générique utilisée avec vingt types est compilée vingt fois, et les macros procédurales lourdes exécutent du code arbitraire au moment de la compilation. Quand une générique chaude n'a pas besoin d'être générique, la monomorphiser manuellement ou réduire ses limites peut réduire mesurément le temps de compilation. C'est un vrai compromis contre l'ergonomie, donc mesurer avant de contorsionner une API.
La granularité des crates coupe deux sens. Un crate géant ne peut pas paralléliser en interne et force la recompilation complète pour les petits édits ; des centaines de petits crates ajoutent le surcharge per-crate et une chaîne de dépendances plus profonde. L'heuristique utile est de diviser le long des frontières qui changent à des taux différents — le code fondamental stable dans son propre crate afin que les édits au code volatil ne le reconstruisent pas.
Les paramètres d'information de débogage et d'optimisation sont le levier structurel le plus peu cher. L'info de débogage complet est chère à générer et lier ; debug = 1 (tables de lignes uniquement) est souvent assez pour les backtraces à une fraction du coût. Et pour les dépendances que vous ne traversez jamais, les overrides opt-level dans un profil vous permettent d'optimiser votre propre code sans payer pour optimiser tout.
Aucun de ces éléments ne sont des changements de configuration, ce pourquoi ils appartiennent après les outils. Mais quand un projet reste lent malgré la mise en cache et un linker rapide, la réponse est presque toujours dans cette liste.
Savoir quand arrêter
Une mise en garde de fermeture : l'optimisation du build est elle-même une tâche avec rendements décroissants, et c'est extraordinairement bon pour se sentir productif. Une fois que votre reconstruction incrémentale est quelques secondes, le tuning supplémentaire achète peu, et les leviers restants deviennent progressivement plus envahissants — restructurer les frontières des crates, couper les dépendances, retravail les génériques. Ceux-ci peuvent valoir le coup de faire, mais ce sont des projets d'ingénierie avec le vrai risque, pas des changements de configuration.
La séquence honnête est : mesurer d'abord, appliquer les fixes de phase bon marché et spécifiques (cache, linker, test runner), mesurer à nouveau, et arrêter quand la boucle ne casse plus votre concentration. L'objectif n'a jamais été un nombre de benchmark — c'était rester en flux assez long pour terminer la pensée que vous aviez quand vous avez frappé sauver.
Le message clé
Les builds lents changent le comportement, pas juste les horaires, et les plus grands fixes en 2026 sont la configuration plutôt que l'ingénierie. Diagnostiquer quelle phase coûte réellement — cargo build --timings et hyperfine battent l'intuition — puis appliquer l'outil qui l'adresse : sccache pour arrêter de recompiler ce que vous ou CI avez déjà construit, mold pour paralléliser le lien sérialisé qui domine les reconstructions incrémentales, cargo-nextest pour des tests plus rapides, isolés, flake-aware avec le sharding CI intégré, et bacon afin que la compilation se produise tandis que vous pensez plutôt que tandis que vous attendez. Vérifiez que chacun s'est réellement engagé, appliquez le cache partagé en CI où le gaspillage est le plus grand, et arrêtez d'optimiser une fois que la boucle cesse de vous interrompre.
Références et ressources
Outils
Contexte et analyse
- Rust Performance Optimization in 2026: A Practical Guide
- The CLI Tools Landscape in 2026 — ToolShelf
- Cargo build timings documentation
Feuilles de triche 1337skills connexes