Profilage de code C++ : trouver les goulots d'étranglement
Les applications C++ modernes deviennent de plus en plus complexes, et même les développeurs expérimentés se retrouvent souvent dans une situation où le programme fonctionne plus lentement que prévu. Au lieu de deviner quelle partie du code ralentit l'exécution, il est nécessaire d'utiliser le profilage. Le profilage est le processus de collecte de données sur les performances d'un programme pendant son exécution, qui permet d'identifier précisément les « goulots d'étranglement » (bottlenecks) et de prendre des décisions éclairées en matière d'optimisation.
Dans cet article, nous examinerons les principaux outils de profilage de code C++ : gprof, Valgrind (Callgrind) et Linux Perf. Vous apprendrez à les utiliser, à interpréter les résultats et à trouver les sections problématiques.
Pourquoi le profilage de code C++ est-il nécessaire ?
Sans profilage, l'optimisation devient une devinette. Vous pouvez passer des heures à « améliorer » une section de code qui ne représente en réalité que 1 % du temps d'exécution. Le profilage fournit des données objectives :
- Identification des points chauds (hot spots) — les fonctions ou les lignes où le programme passe le plus de temps.
- Analyse de la fréquence des appels — combien de fois chaque fonction est appelée.
- Détection d'algorithmes inefficaces — par exemple, une complexité quadratique là où une complexité linéaire était attendue.
- Identification des problèmes de cache et de mémoire — particulièrement important pour les systèmes à forte charge.
Outil 1 : gprof — le profileur GNU classique
gprof est l'un des profileurs les plus anciens et les plus simples à utiliser. Il fonctionne en deux étapes : la collecte de statistiques pendant l'exécution du programme et l'analyse ultérieure des données collectées.
Comment utiliser gprof
Pour commencer, vous devez compiler le programme avec le flag -pg :
g++ -pg -O2 myprogram.cpp -o myprogramEnsuite, exécutez le programme normalement. Une fois terminé, un fichier gmon.out apparaîtra dans le répertoire courant. Pour obtenir le rapport, exécutez :
gprof myprogram gmon.out > analysis.txtExemple d'analyse
Considérons un programme simple qui trie un tableau et effectue une recherche :
#include <algorithm>#include <vector>#include <chrono>#include <iostream>
void slowSearch(const std::vector<int>& data, int target) { for (size_t i = 0; i < data.size(); ++i) { if (data[i] == target) { // Trouvé } }}
int main() { std::vector<int> vec(100000); for (int i = 0; i < 100000; ++i) vec[i] = rand() % 100000; std::sort(vec.begin(), vec.end()); slowSearch(vec, 50000); return 0;}Après le profilage, gprof montrera que la fonction slowSearch prend un temps considérable en raison de la recherche linéaire. La solution est de la remplacer par une recherche binaire (std::binary_search).
Avantages de gprof : simplicité d'utilisation, intégré à GCC.
Inconvénients : ne prend pas en charge le multithreading, nécessite une recompilation, peut fausser les résultats en raison des frais généraux.
Outil 2 : Valgrind (Callgrind) — analyse détaillée
Valgrind avec l'outil Callgrind fournit des informations beaucoup plus détaillées, y compris la simulation du cache et le comptage d'instructions. C'est particulièrement utile pour trouver des micro-optimisations.
Lancement de Callgrind
Pour le profilage, il suffit d'exécuter :
valgrind --tool=callgrind ./myprogramUne fois terminé, un fichier callgrind.out.PID apparaîtra. Pour visualiser les résultats, utilisez l'utilitaire kcachegrind :
kcachegrind callgrind.out.12345Exemple : détection d'allocations inutiles
Considérons un code qui crée souv