Copyright © 2004 Vinayak Hegde
Linux (et les autres Unix) fournit beaucoup de petits utilitaires pratiques que l'on peut combiner pour faire des choses intéressantes. Créer ces logiciels ou les utiliser pour peaufiner vos programmes procure une certaine joie. Dans cette série, nous allons examiner certains de ces outils utiles pour les programmeurs. Ces outils vous aideront à mieux coder et vous faciliteront la vie.
Après avoir conçu et codé un programme, vient le stade de son optimisation. Avant de parler de profilage et d'optimisation en général, j'aimerais attirer votre attention sur deux citations à propos de l'optimisation :
« Il y a plus de péchés informatiques commis au nom de l'efficacité (sans pour autant l'atteindre) qu'au nom de toute autre raison — y compris la stupidité aveugle. » (William A. Wulf)
« Il faut oublier l'efficacité pour disons 97% du temps : l'optimisation prématurée est à la source de tous les maux. » (Donald E. Knuth)
La plupart des programmes suivent en gros la règle connue sous le nom de 80:20 : vous exécutez 20% du code pendant 80% du temps. Comme il est supposé par les citations ci-dessus, le temps du programmeur a plus de valeur que le temps machine. Nous avons donc vu l'avènement de langages comme Java et C#, qui réduisent le temps nécessaire à la programmation et donnent aux programmeurs plus de temps à consacrer à la logique qu'aux subtilités de l'architecture de la machine sous-jacente. Ceci a augmenté le temps d'exécution des programmes mais économisé du temps de programmation. Cependant, nous devons optimiser pour accélérer l'exécution d'un programme. Souvent, les compilateurs le font automatiquement. Par exemple, le compilateur GCC utilise les drapeaux -O (remarquez la majuscule) pour spécifier le niveau d'optimisation. Le profilage est une méthode qui peut nous aider à trouver les sections du code/des fonctions que nous devons optimiser pour améliorer la performance d'un programme. Vous conviendrez qu'il est beaucoup plus intelligent d'optimiser une fonction qui est appelée des milliers de fois quand le programme s'exécute qu'une fonction appelée une dizaine de fois dans un programme. Quand nous profilons un programme, nous arrivons à savoir quelles sont les parties du code fréquemment utilisées et quelles fonctions prennent le plus de temps processeur. Ces deux types de codes sont de bons candidats pour l'optimisation. Comme les données sont collectées en utilisant une trace d'exécution réelle, cette méthode est également efficace pour trouver des bogues cachés. Du fait que vous ne pouvez pas prévoir qu'une fonction donnée sera appelée 1 000 fois pendant l'exécution, il peut s'agir d'un défaut de conception et d'un bogue potentiel. C'est presque aussi utile que les revues de code dans les projets étendus et complexes.
On peut obtenir principalement deux types d'informations de profilage :
Le profil plat (Flat Profile)
Le profil plat détaille combien de temps processeur dure chaque fonction et combien de fois elle a été appelée. C'est un bref résumé des informations de profilage rassemblées. Ceci vous donnera une idée des fonctions qui peuvent être réécrites ou affinées pour gagner en performance.
Le graphe d'appels (Call Graph)
Le graphe d'appels montre pour chaque fonction du code le nombre de fois où elle a été appelée par différentes fonctions, y compris elle-même. Ceci peut suggérer quels sont les appels de fonctions à éliminer ou à remplacer par d'autres fonctions plus efficaces. Ces informations montrent les relations existant entre les différentes fonctions et peuvent être utilisées pour découvrir des bogues dans le code. De plus, vous pouvez optimiser certains chemins du code après avoir examiné les graphes d'appels.
Le code source doit être compilé avec l'option -pg (ainsi qu'avec -g si vous souhaitez un profilage ligne par ligne). Si le nombre de lignes dans le fichier Make est réduit, vous pouvez ajouter ces options à la fin de chaque commande de compilation. Mais si le nombre de commandes de compilation est élevé, vous pouvez définir/redéfinir le paramètre CFLAGS/CXXFLAGS dans le Makefile et l'ajouter à toutes les commandes de compilation du Makefile. Voici un exemple d'utilisation de gprof avec l'utilitaire GNU make :
Décompression de l'archive tar gzipée $ tar zxf make-3.80.tar.gz $ cd make-3.80 Lancement du script configure pour créer les makefiles $./configure [sortie de configure supprimée] |
Nous pouvons utiliser ce make pour compiler d'autres logiciels comme Apache, lynx et cvs. À titre d'exemple, construisons apache à l'aide de ce make. Lorsque nous décompressons, configurons et lançons make sur le source d'Apache, un fichier nommé gmon.out contenant les informations de profilage est généré. Vous pouvez constater que make s'exécute plus lentement que prévu car il enregistre les données de profilage. Il est important de se rappeler que, lors de la collecte des données de profilage, il faut exécuter le programme en lui donnant les entrées que nous lui donnons normalement et en le terminant lorsque tout est terminé. De cette manière, vous aurez simulé un scénario réel pour collecter les données.
Lors de la dernière étape, nous avons obtenu une sortie exécutable appelée gmon.out. Malheureusement, il n'existe pour le moment aucun moyen de spécifier le nom du fichier des données de profilage. Ce fichier gmon.out peut être interprété par gprof pour générer une sortie humainement lisible. La syntaxe correspondante est :
gprof options [Fichier exécutable [fichiers de données de profilage ... ] ] [ > fichier de sortie ] $ gprof make gmon.out > profile-make-with-Apache.txt |
Vous trouverez le fichier complet ici. Voici un extrait du profil plat :
Flat profile: Each sample counts as 0.01 seconds. % cumulative self self total time seconds seconds calls ms/call ms/call name 33.33 0.01 0.01 207 0.05 0.05 file_hash_2 33.33 0.02 0.01 38 0.26 0.26 new_pattern_rule 33.33 0.03 0.01 6 1.67 2.81 pattern_search 0.00 0.03 0.00 2881 0.00 0.00 hash_find_slot 0.00 0.03 0.00 2529 0.00 0.00 xmalloc 0.00 0.03 0.00 1327 0.00 0.00 hash_find_item 0.00 0.03 0.00 1015 0.00 0.00 directory_hash_cmp 0.00 0.03 0.00 963 0.00 0.00 find_char_unquote 0.00 0.03 0.00 881 0.00 0.00 file_hash_1 0.00 0.03 0.00 870 0.00 0.00 variable_buffer_output |
Nous pouvons tirer les conclusions suivantes des données ci-dessus :
Trois fonctions (file_hash_2
, new_pattern_rule
et pattern_search
) représentent presque tout le temps d'exécution.
Il y a six appels de fonction à patter_search
, mais il faut compter une moyenne de 2.81 millisecondes pour chaque appel.
Les informations collectées sont néanmoins insuffisantes. Ce make compilé spécialement a donc été utilisé pour compiler lynx, cvs, make et patch. Tous les fichiers gmon.out renommés ont été rassemblés et les données de profilage ont été compilées à l'aide des commandes suivantes :
$ gprof make gmon-*.out > overall-profile.txt |
Vous trouverez ce fichier ici.
Voici un extrait du profil plat :
Flat profile: Each sample counts as 0.01 seconds. % cumulative self self total time seconds seconds calls ms/call ms/call name 18.18 0.06 0.06 23480 0.00 0.00 find_char_unquote 12.12 0.10 0.04 120 0.33 0.73 pattern_search 9.09 0.13 0.03 5120 0.01 0.01 collapse_continuations 9.09 0.16 0.03 148 0.20 0.88 update_file_1 9.09 0.19 0.03 37 0.81 4.76 eval 6.06 0.21 0.02 12484 0.00 0.00 file_hash_1 6.06 0.23 0.02 6596 0.00 0.00 get_next_mword 3.03 0.24 0.01 29981 0.00 0.00 hash_find_slot 3.03 0.25 0.01 14769 0.00 0.00 next_token 3.03 0.26 0.01 5800 0.00 0.00 variable_expand_string |
Comme nous pouvons le voir, la représentation a quelque peu changé depuis le profil de make que nous avons obtenu en compilant Apache.
Il y a 23 480 appels à la fonction find_char_unquote
et son exécution prend plus de 1/6e du temps
total d'exécution du programme.
La fonction eval
n'est invoquée que 37 fois, mais elle prend tout de même 1/11e du temps d'exécution
du programme. Il est possible que cette fonction fasse beaucoup de choses et soit un bon candidat à un découpage en plusieurs fonctions.
Il est également à noter que chaque appel à eval
prend 4.76 millisecondes, ce qui est assez énorme comparativement
aux autres fonctions.
Les fonctions pattern_search
et update_file_1
prennent presque 1/4 du temps
d'exécution mais ne totalisent que 268 appels. Ces fonctions peuvent peut-être aussi être divisées en de plus petites fonctions.
Jetons maintenant un coup d'½il au graphe d'appels de la compilation d'Apache.
index % time self children called name ----------------------------------------------- 6 eval_makefile [49] [25] 3.7 0.00 0.00 6 eval [25] 0.00 0.00 219/219 try_variable_definition [28] 0.00 0.00 48/48 record_files [40] 0.00 0.00 122/314 variable_expand_string [59] 0.00 0.00 5/314 allocated_variable_expand_for_file [85] 0.00 0.00 490/490 readline [76] 0.00 0.00 403/403 collapse_continuations [79] 0.00 0.00 355/355 remove_comments [80] 0.00 0.00 321/963 find_char_unquote [66] 0.00 0.00 170/170 get_next_mword [88] 0.00 0.00 101/111 parse_file_seq [93] 0.00 0.00 101/111 multi_glob [92] 0.00 0.00 48/767 next_token [70] 0.00 0.00 19/870 variable_buffer_output [68] 0.00 0.00 13/2529 xmalloc [64] 0.00 0.00 2/25 xrealloc [99] 5 eval_makefile [49] ----------------------------------------------- |
Nous pouvons faire les observations suivantes de l'extrait ci-dessus :
La première colonne est une référence à l'index donné à la fin de la sortie de gprof.
La deuxième colonne donne le temps total passé dans la fonction eval
y compris les appels à d'autres
fonctions.
La troisième et la quatrième colonne donnent le temps total passé dans la fonction elle-même et dans ses appels aux autres fonctions.
Le premier nombre de la cinquième colonne donne le nombre d'appels à la fonction depuis eval
, et
le second nombre donne le nombre total d'appels non récursifs à cette fonction par tous ses appelants.
S'il y a des appels récursifs à la fonction elle-même ou à une fonction mutuellement récursive, alors le nom de la fonction
est ajouté à l'ensemble (comme pour eval_makefile
et eval
ci-dessus).
Certaines fonctions sont toujours appelées par eval
. On peut y trouver un avantage dans certains cas
si le temps dédié à l'appel de la fonction lui-même peut être éliminé.
L'utilisation de gprof vous permet aussi d'obtenir un listing annoté et un profilage ligne par ligne. Ceci peut être utile une fois que vous avez identifié les sections de code qui doivent être optimisées. Ces options vous aideront à creuser le code source pour y trouver les insuffisances. Le profilage ligne par ligne conjugué au profil plat peut être utilisé pour vérifier quels sont les chemins du code fréquemment exécutés. Le listing annoté peut être utilisé pour creuser les appels de fonctions eux-mêmes jusqu'au bloc de base (boucles et instructions de branchement) pour découvrir quelles boucles sont le plus exécutées et quelles branches sont prises le plus fréquemment. Ceci est utile pour affiner le code pour une performance optimale. Il existe d'autres options qui sortent du cadre de cet article. Reportez-vous à la documentation de gprof pour plus de détails. Il existe une interface KDE à gprof appelée kprof. Reportez-vous à la section Ressources pour connaître l'URL.
Les outils de profilage comme gprof peuvent être d'une grande aide dans l'optimisation des programmes. Le profilage est une des premières étapes de l'optimisation manuelle pour trouver les goulots d'étranglement et les supprimer.
La page GNU Info de gprof
L'interface KDE à gprof KProf
Function Check — un autre outil de profilage. Il permet de contourner certains défauts de gprof.
Vinayak suit en ce moment un cursus d'APGDST au NCST. Il s'intéresse aux réseaux, aux systèmes de calcul parallèles et aux langages de programmation. Il pense que Linux aura le même effet sur l'industrie du logiciel que celui que l'imprimerie a eu sur le monde de la science et de la littérature. Pendant ses loisirs inexistants, il écoute de la musique et lit des livres. Il travaille actuellement au projet LIberatioN-UX pour lequel il rend accessible des solutions Linux peu onéreuses pour les milieux scolaires ou d'entreprise en configurant des stations amorçables à distance (clients légers).
Copyright © 2004, Vinayak Hegde.
Copying license http://www.linuxgazette.com/copying.html.
Paru dans le n°100 de la Linux Gazette de mars 2004.
Traduction française par Isabelle Hurbain.
<isabelle POINT hurbain CHEZ pasithee POINT net>
.
Relecture de la traduction française par Joëlle Cornavin
<jcornavi CHEZ club TIRET internet POINT fr>
.