10 KiB
TD R5.A.04 — Profiling avec gprof
Objectif et environnement
Analyser l'analyseur de fréquence de mots, identifier les coûts, optimiser une
chose à la fois et conserver exactement les résultats. Travail réalisé le
7 octobre 2026 dans le fork local existant couchot/2026_TD1_DEV51_Qualite_Algo.
Les commits sont locaux ; aucun envoi au serveur n'a été effectué.
- Linux x86_64, Intel Core i7-8700 à 3,20 GHz, 12 processeurs logiques.
- GCC 16.2.1, outils GNU
timeetgprof. - Même scénario : 250 000 mots, vocabulaire de 7 000 mots, graine
20261006UL. - Toutes les comparaisons utilisent
-O0 -g, conformément au TD. - Trois exécutions sans
-pgpar version ; médiane du temps réel retenue. - Le pic mémoire est le maximum de mémoire résidente indiqué par GNU
time(%M, en Kio), et non une mesure des allocations totales.
La source initiale est conservée dans mesures/analyseur_avant.c. Les sorties,
mesures brutes et le rapport initial sont dans mesures/.
1. Mesurer avant d'optimiser
gcc -O0 -g -Wall -Wextra analyseur.c -o analyseur
/usr/bin/time -f 'reel=%e utilisateur=%U systeme=%S memoire_Kio=%M' ./analyseur
Avant modification : temps réels 3,14 / 3,01 / 2,91 s, médiane 3,01 s ; temps utilisateur 3,09 / 2,99 / 2,89 s, médiane 2,99 s. Pic mémoire médian : 6 668 Kio.
Résultats de référence :
- 21 746 lignes, 250 000 mots, 5 866 mots distincts.
- Longueur moyenne : 6,712 ; écart-type : 1,873.
- Bigramme
me: 17 607 occurrences, souvent suivi den. - Premier mot :
rane, 287 occurrences. - Empreinte :
1d53de903efd2b7c.
2. Profiler
gcc -O0 -g -pg analyseur.c -o analyseur
/usr/bin/time ./analyseur
mv gmon.out gmon_avant.out
gprof -b ./analyseur gmon_avant.out > profil_avant.txt
Les binaires et gmon avant/après ont été conservés séparément pendant le travail
pour ne pas analyser un ancien profil avec un nouveau binaire. Les profils texte
sont versionnés ; les binaires et fichiers gmon sont reproductibles et ignorés.
Avec instrumentation, avant : 6,64 s réelles, 6,58 s utilisateur. Le profil plat cumule 2,71 s échantillonnées. Ces valeurs ne sont pas interchangeables : instrumentation, échantillonnage, appels de bibliothèque non instrumentés et attentes influencent les mesures. Le gain global est donc calculé sur les exécutions sans instrumentation.
3. Analyser le profil plat et le graphe d'appels
| Fonction | Temps propre | Part du profil | Appels |
|---|---|---|---|
longueur |
0,92 s | 33,76 % | 36 009 819 |
egales |
0,89 s | 32,84 % | 308 701 874 |
chercher_mot |
0,37 s | 13,65 % | 150 182 |
comparer |
0,12 s | 4,61 % | 12 172 087 |
longueur_moyenne |
0,11 s | 4,06 % | 5 867 |
doit_echanger |
0,05 s | 1,85 % | 17 202 045 |
Le graphe explique les appels :
main → compter_mots_ligne → compter_mot → chercher_mot → egales.chercher_motprovoque 296 656 203 comparaisons, car il parcourt les entrées déjà connues pour chaque mot retenu. Coût O(M × U), avec M le nombre de mots non vides et U le nombre de mots distincts.main → ecart_type_longueur → longueur_moyenne → longueur. La moyenne est recalculée 5 866 fois dans la boucle de variance : 34 415 822 appels àlongueurviennent du calcul de moyenne. Les statistiques sont inutilement O(U²).main → trier_entrees → doit_echanger → comparer. Le tri à bulles effectue U(U−1)/2 = 17 202 045 comparaisons, même sans nécessité d'échanger : O(U²).normaliser_ligne → longueur: 1 561 330 appels. La longueur est parcourue à chaque condition de boucle : O(L²) par ligne.construire_rapport → ajouter → longueur: 5 867 appels àajouter. Chaque ajout reparcourt tout le rapport déjà construit. Pour une longueur moyenne de ligne bornée, le coût devient O(U²).
Le graphe attribue environ 45,2 % du temps cumulé à chercher_mot, 36,3 % à la
variance et 7,6 % au tri. Ce sont des estimations : gprof répartit le temps d'un
appelé entre ses parents en proportion du nombre d'appels. Un appel à longueur
sur un rapport de milliers de caractères coûte bien davantage qu'un appel sur
un mot ; cette répartition peut donc sous-estimer la construction du rapport.
Un temps affiché à 0,00 s ne signifie pas un coût nul.
4. Pistes proposées, classées par gain attendu
- Indexer les mots par hachage : supprimer les centaines de millions de comparaisons linéaires. Gain attendu très élevé.
- Calculer la moyenne une seule fois avant la boucle de variance : supprimer un parcours complet par mot distinct. Gain attendu élevé.
- Remplacer le tri à bulles par un tri en O(U log U). Gain attendu important.
- Normaliser les lignes en un seul parcours. Gain attendu plus modéré.
- Conserver la position d'écriture dans le rapport. Gain potentiellement important malgré son faible poids apparent dans le graphe d'appels.
- Ensuite, indexer les mots vides ou filtrer par longueur avant comparaison.
Piste laissée pour une seconde itération : le profil final permet d'en
évaluer la priorité. Compiler en
-O2serait aussi utile pour un produit livré, mais ferait varier un autre facteur que les algorithmes dans ce TD.
5. Optimisations réalisées et mesures intermédiaires
Chaque ligne ajoute une seule optimisation à la version précédente. Chaque
version a été compilée avec -Wall -Wextra sans avertissement et exécutée trois
fois. Le rapport et la sortie standard ont été comparés octet par octet à la
référence après chaque exécution.
| Version cumulative | Commit | Temps réels (s) | Médiane (s) | Gain sur l'étape précédente |
|---|---|---|---|---|
| Initiale | 6165b37 |
3,14 / 3,01 / 2,91 | 3,01 | — |
| Index de hachage | 2db740d |
1,50 / 1,48 / 1,47 | 1,48 | 50,8 % |
| Moyenne hors boucle | 5d71287 |
0,88 / 0,90 / 0,86 | 0,88 | 40,5 % |
| Tri fusion | dddcfd4 |
0,67 / 0,62 / 0,61 | 0,62 | 29,5 % |
| Normalisation linéaire | 6ab37d6 |
0,47 / 0,46 / 0,47 | 0,47 | 24,2 % |
| Position d'écriture du rapport | 1d07578 |
0,18 / 0,14 / 0,20 | 0,18 | 61,7 % |
Détails et compromis :
- Hachage : 65 536 cases, indices vers les entrées et sondage linéaire pour résoudre les collisions. Une égalité de hachage ne suffit jamais : les chaînes sont comparées. L'ordre d'insertion des entrées est conservé. Charge de 9 % sur ce scénario, au plus 31 % pour 20 000 entrées ; environ 256 Kio supplémentaires. Recherche en O(1) en moyenne, O(U) au pire. L'index sert au comptage et n'est plus consulté après le tri.
- Variance : la moyenne ne dépend pas de l'indice ; la calculer avant la boucle ramène le calcul à O(U), en conservant l'ordre de sommation flottante.
- Tri fusion : même ordre, occurrences décroissantes puis mots croissants. O(U log U) garanti, tampon temporaire O(U), soit 211 176 octets ici.
- Normalisation : arrêt sur le caractère nul, O(L) sans parcours répétés.
- Rapport : écriture directe à l'offset courant avec
snprintfet contrôle de la capacité. Travail proportionnel à la taille produite, sans rescan du préfixe et sans buffer de ligne intermédiaire.
6. Re-profiler et valider
Le profil final est dans profil_apres.txt :
| Indicateur gprof | Avant | Après |
|---|---|---|
Appels à egales |
308 701 874 | 12 195 414 |
Appels à longueur |
36 009 819 | 38 532 |
Appels à longueur_moyenne |
5 867 | 2 |
Appels à doit_echanger |
17 202 045 | 66 114 |
Appels à comparer |
12 172 087 | 33 591 |
| Temps propre total échantillonné | 2,71 s | 0,14 s |
| Temps réel instrumenté | 6,64 s | 0,34 s |
Après optimisation, egales représente 60,71 % du profil, essentiellement
pour la recherche des mots vides : celle-ci conserve 12 045 671 appels.
L'index des mots ne provoque plus que 149 743 appels à egales.
analyser_bigrammes représente 14,29 %. Sur seulement environ 14 échantillons,
ces pourcentages sont peu précis ; les nombres d'appels restent plus utiles.
La récursion du tri fusion est présentée de manière spécifique par gprof :
le chiffre 1 du profil plat n'est pas le nombre total d'invocations récursives.
Sans instrumentation : 3,01 / 0,18 = 16,72, soit un programme environ 16,7 fois plus rapide et une réduction du temps réel de 94,0 %. La médiane du temps utilisateur passe de 2,99 à 0,12 s (réduction de 96,0 %). Le pic mémoire médian passe de 6 668 à 6 980 Kio (+312 Kio, soit +4,7 %). Les pics mesurés reflètent les allocations touchées et l'allocateur ; ils ne sont pas la somme des tailles théoriques des structures.
L'empreinte reste 1d53de903efd2b7c à toutes les étapes. La comparaison
binaire confirme aussi l'identité du rapport complet et de toute la sortie,
notamment des statistiques que l'empreinte du rapport ne couvre pas.
AddressSanitizer et UndefinedBehaviorSanitizer n'ont signalé aucune erreur sur
le scénario du TD. LeakSanitizer ne fonctionne pas sous le traçage de cet
environnement ; la vérification a donc été relancée avec
ASAN_OPTIONS=detect_leaks=0. L'absence de fuite n'est pas certifiée par ce test.
Reproduire
Depuis la racine du dépôt, avec GCC, GNU time, gprof et Python 3 :
python3 reproduire_mesures.py
Le script compile les sources avant/après, mesure trois exécutions de chacune,
compare rapports et sorties, puis produit les profils dans un nouveau dossier
mesures_reproduction/. Il refuse d'écraser un dossier existant ; utiliser
--destination /tmp/autre_mesure pour une nouvelle campagne. Il a également
été exécuté intégralement pour vérifier son fonctionnement : cette seconde
campagne donne des médianes de 2,88 s avant et 0,11 s après (environ ×26,2).
Les fichiers texte correspondants sont conservés dans mesures/verification/.
La variation du gain confirme la sensibilité des mesures courtes ; le bilan
principal utilise les mesures de la première campagne, sans mélanger les deux.
Les chiffres dépendent de cette machine, de sa charge et de ce scénario. Les versions finales étant courtes, les variations relatives sont plus fortes (0,14 à 0,20 s). Une campagne plus longue et un scénario représentatif seraient nécessaires pour généraliser ces gains à d'autres usages.