Partie 2 : Profiling, PGO & LTO
Mesurer PGO et LTO sur une simulation SPH en C++ avec perf et des flamegraphs, et pourquoi ils n'ont presque rien apporté.
Un guide pas à pas testé pour activer le Profile Guided Optimization et le Link Time Optimization dans un projet Meson compilé avec Clang, y compris l'étape de fusion llvm-profdata que la plupart des guides oublient.
J’ai expliqué pourquoi PGO et LTO n’ont presque rien changé sur ma simulation SPH dans la partie 2 de ma série sur l’optimisation. Cet article laisse de côté la question « est-ce que ça aide vraiment » et se contente de vous donner les commandes, parce que la moitié des guides que j’ai trouvés en ligne passent sous silence une étape qui fera échouer votre build avec une erreur clang obscure.
PGO (Profile Guided Optimization) exécute une version instrumentée de votre programme sur des entrées réelles, enregistre quelles branches et fonctions sont effectivement exécutées, puis réinjecte ces données dans une seconde compilation pour que le compilateur puisse organiser les chemins chauds/froids et inliner en fonction de preuves plutôt que de suppositions.
LTO (Link Time Optimization) permet au compilateur d’optimiser à travers les unités de compilation plutôt qu’un seul fichier .cpp à la fois, ce qui lui permet d’inliner à travers les fichiers, d’éliminer le code mort, et de voir à travers des appels qui seraient autrement opaques au niveau du fichier objet.
Ils sont orthogonaux. Vous pouvez utiliser l’un ou l’autre séparément, ou cumuler les deux pour le build de production final.
b_lto_mode).lld n’est pas obligatoire, mais il simplifie la mise en cache du thin LTO. GNU ld et gold prennent tous les deux en charge le LTO via l’interface plugin, c’est juste plus de rouages.meson setup build --buildtype=release -Db_lto=true
meson compile -C buildC’est le LTO complet : chaque unité de compilation est regroupée en un seul module géant au moment du lien. C’est exhaustif, et c’est lent, à la fois en temps de lien et en RAM utilisée par le linker. Sur un gros projet, ça peut faire mal.
Le thin LTO résout ça en conservant l’optimisation par module tout en partageant des résumés entre les modules, ce qui permet au linker de paralléliser l’étape de génération de code :
meson setup build --buildtype=release -Db_lto=true -Db_lto_mode=thin -Db_lto_threads=8
meson compile -C buildSi vous voulez confirmer que meson transmet bien le flag et ne l’ignore pas silencieusement, passez -v à votre build ninja :
ninja -C build -v | grep -o '\-flto[^ ]*'
-flto=thinVous voulez spécifiquement lld ? Définissez-le avant meson setup, pas comme option -D :
CXX_LD=lld meson setup build --buildtype=release -Db_lto=true -Db_lto_mode=thinC’est la partie en deux passes.
meson setup build-pgo --buildtype=release -Db_pgo=generate
meson compile -C build-pgo
./build-pgo/your-binary --some-representative-workloadExécuter le binaire instrumenté dépose un fichier à côté de l’endroit d’où vous l’avez lancé, nommé quelque chose comme default_15853201381332895877_0.profraw. Pas dans le répertoire de build. Là où se trouvait votre shell actuel au moment où vous avez lancé le binaire.
Meson ne fusionne pas le profil pour vous. Il n’y a aucune chaîne profdata ou profraw nulle part dans le code source de mesonbuild. Si vous reconfigurez simplement avec -Db_pgo=use et compilez, vous obtenez ceci :
clang: error: Error in reading profile default.profdata: No such file or directory
ninja: build stopped: subcommand failed.Clang veut un default.profdata déjà fusionné et indexé, et il le cherche dans son propre répertoire de travail, qui, pour le backend ninja, est la racine de votre répertoire de build. Alors fusionnez-le vous-même à cet endroit :
llvm-profdata merge -output=build-pgo/default.profdata *.profrawEnsuite reconfigurez et recompilez :
meson configure build-pgo -Db_pgo=use
meson compile -C build-pgoSi vous avez plusieurs charges de travail représentatives, exécutez le binaire instrumenté une fois par charge de travail et donnez tous les fichiers .profraw résultants au même appel llvm-profdata merge. Plus de couverture, un seul profil.
-Db_pgo=generate paient le prix de compteurs sur chaque branche et chaque appel de fonction, et ce n’est pas une erreur d’arrondi.Pour un vrai build de release, générez d’abord le profil sur un build optimisé simple (itération plus rapide, et LTO ne vous aide pas à trouver les chemins chauds, il agit seulement dessus), puis effectuez le build final avec les deux flags ensemble :
meson configure build-pgo -Db_pgo=use -Db_lto=true -Db_lto_mode=thin
meson compile -C build-pgob_lto_mode=thin avant de lui balancer plus de RAM.default.profdata fusionné dans le répertoire de build signifie pas de build -Db_pgo=use.Si vous vous demandez si tout cela vaut la complexité de build supplémentaire pour votre cas d’usage, c’est justement le sujet de la partie 2 : sur une boucle numérique avec peu de branches, ça m’a fait gagner environ 3 %. Les résultats varieront selon le nombre de branches que contient réellement votre chemin chaud.
Mesurer PGO et LTO sur une simulation SPH en C++ avec perf et des flamegraphs, et pourquoi ils n'ont presque rien apporté.
Avant de toucher la moindre ligne de ma simulation de fluide, j'ai laissé le compilateur faire le travail. Changer simplement les flags m'a fait passer de 1x à environ 40x.
Multiboot2 vous dépose en mode protégé 32 bits ; Limine vous fournit un environnement 64 bits. Un unique trampoline d'entrée qui gère les deux sans forker le noyau.