What the Fork - outil de visualisation de build C/C++/Rust en temps réel
(danielchasehooper.com)- What the Fork est un outil cross-platform qui visualise en temps réel divers processus de build, notamment C/C++/Rust
- Il permet d’identifier facilement des problèmes structurels comme le manque de parallélisme ou des processus inefficaces dans les systèmes de build existants
- Il fonctionne avec tous les systèmes de build et tous les langages de programmation, avec prise en charge de make, ninja, gradle, zig, cargo et d’autres outils de build
- Grâce à la surveillance des appels système, il visualise sous forme de boîtes le temps d’exécution, les commandes et les relations de dépendance de chaque processus
- C’est un outil très utile pour l’optimisation des builds, l’analyse des goulots d’étranglement et l’amélioration des performances CI
Présentation et contexte
- What the Fork est un outil de visualisation de build en temps réel développé pour diagnostiquer visuellement les causes des builds lents
- Dans un projet comme LLVM, le volume de code peut à lui seul ralentir la compilation, mais la plupart des builds prennent inutilement trop de temps à cause de configurations inefficaces
- Jusqu’à présent, il était difficile d’inspecter directement les problèmes d’un build ou d’avoir une vue d’ensemble claire de ses défauts structurels, d’où le besoin d’un tel outil
- L’outil a été conçu pour être cross-platform et peut s’appliquer à tous les systèmes de build et à tous les langages
Principales fonctionnalités et utilisation
- What the Fork n’est pas un simple profileur système, mais un outil conçu pour diagnostiquer des problèmes spécifiques aux builds
- Il permet par exemple de repérer l’absence du flag
-javec make, des concentrations de temps sur un fichier ou une étape de compilation précise, ou encore des commandes exécutées séquentiellement alors qu’un traitement parallèle serait possible - Il est particulièrement efficace pour analyser et optimiser les performances des clean builds dans les environnements CI
- L’utilisation consiste à préfixer la commande de build avec
wtf(par exemple :wtf make,wtf cargo build,wtf npm run build) - Quand le build démarre, l’interface se lance et met à jour en temps réel l’avancement de chaque processus
Interface et mode de visualisation
- Chaque processus de build est affiché sous forme de boîte sur une timeline, avec un code couleur selon le type
- Les relations parent-enfant entre les processus sont représentées par une structure imbriquée
- Le panneau inférieur affiche le temps d’exécution, le répertoire de travail et les arguments complets de la commande pour le processus sélectionné
Principe de fonctionnement
- Un build est une combinaison de plusieurs processus (par exemple
bash,clang,ld) - Les builds de grande taille utilisent divers outils comme
cargo,make,bazel,gradle,xcodebuild, qui exécutent en pratique de nombreuses commandes et gèrent dépendances, cache et ordonnancement - La seule sortie du terminal ne permet pas de comprendre les processus imbriqués (par exemple
ldinvoqué en interne parclang) ni la structure détaillée du timing - Pour cela, l’outil exploite des appels système propres à chaque OS pour détecter le démarrage et la fin des processus (macOS : Endpoint Security API, Linux : ptrace(), Windows : Event Tracing for Windows, etc.)
- Cette approche permet de reconstruire l’ensemble du build et sa timeline, puis d’identifier le chemin d’exécution et la durée de chaque étape
- Il peut aussi servir au suivi de divers sous-processus au-delà des builds
Cas réels et observations
- Plusieurs ingénieurs (chez Delta, Mozilla et Apple) l’ont réellement appliqué à leurs projets et ont découvert des problèmes inattendus
- Exemple 1 : sur un projet open source utilisant Cargo, les fichiers étaient compilés séquentiellement, révélant un manque de parallélisme (avec une possibilité d’amélioration de vitesse de plus de 10x sur un CPU à 10 cœurs)
- Exemple 2 : sur un build LLVM avec Ninja, tous les cœurs CPU travaillaient efficacement en parallèle, atteignant une efficacité de build idéale
- Exemple 3 : sur un projet basé sur CMake, une structure inefficace a été mise au jour, avec des exécutions imbriquées de cmake/make/clang et 85 répétitions de vérifications de version Xcode/OS, alors que le travail réel était minime
- Exemple 4 : sur un gros projet Objective-C utilisant xcodebuild, un manque de parallélisme a été observé en fin de build, ainsi qu’une période d’inactivité de 6 secondes avant le démarrage, alors que ninja commence la compilation presque immédiatement après 0,4 seconde
- Exemple 5 : lorsque Zig compile Orca Project, l’ordre de build des dépendances est déterminé de manière aléatoire, ce qui fait varier l’efficacité du parallélisme selon la chance. Certaines dépendances s’exécutent en dernier, ce qui dégrade le parallélisme
- Exemple 6 : sur le projet GitHub CLI utilisant make/go, le téléchargement des dépendances prend beaucoup de temps. Réduire les dépendances pourrait améliorer la vitesse du build
Effets pratiques et limites
- L’analyse visuelle de la timeline permet d’identifier des goulots d’étranglement inattendus, des répétitions inutiles de dépendances et des zones où le parallélisme est insuffisant
- Elle permet de repérer rapidement des axes d’amélioration structurels — problèmes de dépendances, travail inutilement refait, inefficacité d’un outil donné — et de les exploiter directement pour optimiser les performances de build
- L’affichage de la commande complète de chaque processus permet une analyse plus fine
Programme bêta
- What the Fork fonctionne sur Windows, Linux et macOS
- Les particuliers et les équipes souhaitant faire un retour peuvent demander à rejoindre la bêta privée (lien Google Forms fourni)
3 commentaires
La commande est vraiment trop drôle, non ?
wtf, carrément…Ce serait bien que cela sorte en open source.
Avis Hacker News
Je suis actuellement coincé dans un environnement où seuls CMake, GCC et Unix Make sont utilisables, donc il est presque impossible d'obtenir des informations détaillées sur les raisons de la lenteur d'un build. Le build est un vrai chaos, avec des étapes complexes comme la copie de fichiers du code source vers le dossier de build, plusieurs langages (C, C++, Fortran, Python), des étapes CMake personnalisées, etc. Si cet outil fonctionne bien même dans ce type d'environnement désordonné, j'ai l'impression qu'on pourrait vraiment apprendre énormément de choses.
tsoding a écrit une bibliothèque C single-header pour les builds multiplateformes sur https://github.com/tsoding/nob.h, qui ne nécessite que
cc. On peut examiner les étapes du build avec un outil de profiling GDB. Je trouve l'idée excellente. Ce n'est peut-être pas adapté à l'auteur de ce billet, mais si vous devez gérer plusieurs langages, Nix est un excellent outil de build.J'ai moi-même créé un outil de traçage/profiling du temps de compilation sous forme de plugin GCC. Si ça vous intéresse : https://github.com/royjacobson/externis
Quand j'essayais de réduire le temps de compilation de mon moteur de jeu, j'utilisais la taille des artefacts compilés comme métrique de substitution. Le temps wall-clock était tellement instable que mesurer la taille binaire, identique d'un build à l'autre et même sur différentes machines, était en fait plus simple à exploiter. Ce n'est pas correct à 100 %, mais en pratique c'était utile.
J'ai un problème similaire. J'ai souvent vu CMake recompiler des fichiers qui n'avaient même pas été modifiés. Par exemple, il suffit parfois d'un petit changement dans un
.cpp, sans changement d'interface, pour que des objets totalement indépendants soient recompilés. Je me demande parfois si CMake ne crée pas des dépendances plus fortes que celles des fichiers réels, ce qui allongerait inutilement le temps de build.Je suggérerais à l'auteur du blog d'afficher tout en haut de la page, juste sous l'en-tête, le gif montrant le build de l'application macOS. C'est plus agréable de voir d'abord le résultat produit, puis l'explication.
J'aime vraiment beaucoup ce projet. J'ai fait quelque chose de similaire en 2018 avec strace, dtruss et https://buildinfer.loopperfect.com/, notamment pour générer automatiquement des fichiers BUCK. La visualisation se faisait avec graphviz, perfetto.dev, etc. C'est dommage de ne pas avoir réussi à en faire un produit packagé, mais en conseil cela nous a beaucoup aidés à diagnostiquer les causes et à migrer vers BUCK/Bazel. Récemment, j'y suis revenu en réfléchissant à des usages plus larges. Il y a aussi des défis techniques intrinsèques à cette approche : si les logs de syscalls sont écrits sur disque, ils peuvent grossir jusqu'à plusieurs dizaines voire centaines de Go (par exemple llvm atteint 50 Go, et dans certains cas plus de 100 Go) ; il faut aussi bien gérer les étapes de build qui passent par https, l'IPC, etc. ; et comme l'analyse se fait à l'exécution, il faut la répéter pour chaque configuration de build.
Je trouve ça génial. J'ai souvent eu l'impression qu'on passait à côté de nombreux problèmes faute de ce genre de visualisation. Il y a dix ans, quand j'optimisais le système de build de Mozilla, un tel outil aurait été extrêmement utile. J'aurais aimé que l'article explique davantage quels problèmes concrets il a permis d'identifier.
Sur des projets C++ gérés avec CMake, j'ai déjà utilisé avec succès ninjatracing et le
-ftime-tracede Clang pour visualiser les performances de build. En ajoutant ClangBuildAnalyzer, on peut analyser plus finement où le compilateur passe son temps.Je trouve ça vraiment très cool. Y a-t-il un projet de passage en open source ? Je construis quelque chose de similaire et j'aimerais collaborer.
Si vous utilisez le compilateur Visual C++ sous Windows, je recommande aussi vcperf. Il est inclus par défaut dans VS2022, ou peut être compilé directement depuis GitHub. Je l'ai aussi utilisé sur des projets générés par UBT ou CMake. Je ne me souviens plus s'il permet de juger directement la qualité de la parallélisation du build, mais il donne facilement accès aux informations du frontend du compilateur. Il permet notamment de repérer facilement les fichiers d'en-tête fréquemment inclus ou intrinsèquement lourds.
C'est en général négligé, mais c'est une observation importante : la logique de build « baked in » (pré-calculée) dans le système de build n'assure pas un suivi assez fin de l'impact réel des modifications. Par exemple, dans ninja, une partie de la logique de build est injectée à l'avance, ce qui le rend rapide. J'ai fait un benchmark de build complet de Xerces-C++ avec ninja (configuré via CMake) et build2 (un outil qui gère dans le build la configuration et le suivi des changements) : ninja prend 3,23 s, build2 3,54 s. Si on relance le build en conservant une partie des fichiers générés par CMake, on descend à 3,28 s. À titre de référence, l'étape de configuration propre à CMake seule prend 4,83 s. Un build CMake+ninja sur toute la pile prend donc en réalité autour de 8 secondes, ce qui correspond au temps que l'on subit en pratique quand on utilise cette bibliothèque.
J'ai déjà essayé quelque chose de similaire en lançant Instruments pendant le build pour voir quel processus faisait quoi et à quel moment. L'inconvénient, c'est qu'Instruments devient poussif quand le build dure longtemps, et l'absence de filtrage de l'arbre des processus est pénible, mais cela a été d'une grande aide quand Twitter a fortement réduit les temps de build de son code iOS. Récemment, le tracing « All Processes » d'Instruments s'est mis à casser, donc cette méthode n'est plus possible actuellement.
Je trouve ça vraiment impressionnant. Existe-t-il déjà une version macOS que l'on puisse tester tout de suite ? J'aimerais l'essayer aussi sur des projets Rust ou C++/Swift.
Il n'est pas prévu de distribuer une version macOS à d'autres bêta-testeurs avant la correction des bugs, mais si vous faites une demande (en bas de l'article) en mentionnant ce commentaire, je veillerai à vous inclure dans le groupe bêta.
Il ne semble pas encore y avoir de release publique sur aucun OS, seulement un formulaire d'accès anticipé.