Nicolas Repentin
nicolas.repentin@gmail.com 2009
Gérard Baylard
Geodebay@gmail.com 2010
Bruno Brouard
annoa.b@gmail.com 2010,2012
Yoann Fievez
yoann.fievez@gmail.com 2013

# Utilisation de Massif pour profiler l’utilisation de la mémoire des logiciels GNOME.

Cet article décrit comment utiliser le profileur de tas Massif avec les applications GNOME. Nous décrivons comment appeler, interpréter et agir sur la sortie de Massif. Le jeu Swell Foop est utilisé comme un exemple.

# Introduction

Massif fait partie de la suite d’outils de profilage mémoire valgrind. Son but est de donner une vue détaillée de l’utilisation de mémoire dynamique pendant la durée du programme. Plus précisément, il enregistre l’utilisation de la mémoire du tas et de la pile.

Le tas est une zone mémoire dans laquelle des fonctions comme `malloc` font leurs allocations. Le tas augmente à la demande et est généralement la plus grande zone mémoire d’un programme. La pile est l’endroit où toutes les données locales des fonctions sont stockées, y compris les variables « automatiques » du C et l’adresse de retour des sous-programmes. La pile est habituellement beaucoup plus petite et beaucoup plus active que le tas. Nous ne la prendrons pas explicitement en considération puisque Massif traite la pile comme faisant partie du tas. Massif fournit également des informations sur les quantités de mémoire utilisées pour la gestion du tas.

Massif produit deux fichiers de sortie : un aperçu graphique dans un fichier postscript et une analyse détaillée dans un fichier texte.

# Utilisation de Massif avec GNOME

Massif possède très peu d’options et de nombreux programmes n’en ont pas besoin. Toutefois, pour les applications GNOME, où l’allocation de mémoire pourrait être profondément enfouies dans glib ou GTK, le nombre de niveaux de la pile des appels où Massif descend doit être augmenté. Ce résultat est obtenu en utilisant le paramètre --depth. Par défaut c’est 3 ; l’augmenter à 5 garantira que la pile d’appel descend jusqu’à votre code. Un ou deux autres niveaux peuvent également être souhaitable pour fournir à votre code un certain contexte. Puisque le niveau de détail devient rapidement excessif, il est préférable de commencer avec un paramètre de profondeur faible et de ne l’augmenter que lorsqu’il devient évident qu’il n’est pas suffisant.

Il est également utile d’indiquer à Massif quelles fonctions allouent de la mémoire dans `glib`. Cette indication supprime une couche superflue d’appels de fonctions dans les rapports et permet d’avoir une idée plus claire sur les parties de code qui allouent de la mémoire. Les fonctions d’allocation dans `glib` sont : `g_malloc`, `g_malloc0`, `g_realloc`, `g_try_malloc` et `g_mem_chunk_alloc`. Utilisez l’option `--alloc-fn` pour les indiquer à Massif.

Votre ligne de commande devrait par conséquent ressembler à quelque chose comme :

valgrind --tool=massif --depth=5 --alloc-fn=g_malloc --alloc-fn=g_realloc --alloc-fn=g_try_malloc \ --alloc-fn=g_malloc0 --alloc-fn=g_mem_chunk_alloc swell-foop

Swell Foop est le programme que nous allons utiliser comme exemple. Sachez que, puisque valgrind émule la CPU, il fonctionne très lentement. Vous aurez aussi besoin de beaucoup de mémoire.

# Interprétation des résultats

La sortie graphique de Massif est tout à fait explicite. Chaque bande représente la mémoire allouée par une fonction au fil du temps. Après avoir identifié les bandes qui utilisent le plus de mémoire, généralement les plus épaisses en haut, vous devez consulter le fichier texte pour les détails.

Le fichier texte est constitué de paragraphes hiérarchisés, avec en tête de liste les utilisateurs de mémoire les plus gourmands rangés en ordre décroissant d’espace*temps. Au-dessous se trouvent d’autres paragraphes ; chacun analyse les résultats de plus en plus finement au fur et à mesure que vous vous enfoncez dans les profondeurs de la pile des appels de fonctions. Pour illustrer cela, nous utiliserons la sortie de la commande précédente.

## Sortie de Massif pour la version non optimisée du programme Swell Foop.

L’image ci-dessus montre une sortie postscript typique de Massif. C’est le résultat que vous obtiendriez en jouant une partie à un joueur de Swell Foop (version 2.8.0), et en quittant. Le fichier postscript aura un nom comme massif.12345.ps et le fichier texte sera appelé massif.12345.txt. Le nombre du milieu est l’identifiant (ID) du processus correspondant au programme examiné. Si vous testez réellement cet exemple, vous obtiendrez deux versions de chaque fichier, avec des nombres légèrement différents. C’est parce que Swell Foop lance un deuxième processus que Massif analyse également. Nous allons ignorer ce deuxième processus, car il consomme très peu de mémoire.

En haut du graphique, est affichée une grande bande jaune étiquetée `gdk_pixbuf_new`. Elle semble être la candidate idéale pour une optimisation, mais nous avons besoin d’utiliser le fichier texte pour savoir qui appelle `gdk_pixbuf_new`. En tête du fichier texte, il y a quelque chose comme :

Command: ./swell-foop

== 0 =========================== Heap allocation functions accounted for 90.4% of measured spacetime

Called from: 28.8% : 0x6BF83A: gdk_pixbuf_new (in /usr/lib/libgdk_pixbuf-2.0.so.0.400.9)

6.1% : 0x5A32A5: g_strdup (in /usr/lib/libglib-2.0.so.0.400.6)

5.9% : 0x510B3C: (within /usr/lib/libfreetype.so.6.3.7)

3.5% : 0x2A4A6B: __gconv_open (in /lib/tls/libc-2.3.3.so)

Le numéro dans la ligne avec les signes « = » indique la profondeur d’imbrication dans la pile des appels de fonctions où nous nous trouvons ; dans notre cas, nous sommes au sommet. Suit la liste des utilisateurs d’espace*temps mémoire du plus gourmand au moins gourmand. L’espace*temps mémoire représente la quantité de mémoire occupée multipliée par le temps d’utilisation. Il correspond à la surface des bandes sur le graphe. Cette première partie du fichier texte confirme ce que nous savions déjà : le gros de l’espace*temps est occupé par `gdk_pixbuf_new`. Pour savoir qui appelle `gdk_pixbuf_new`, il est nécessaire de rechercher plus bas dans le fichier texte :

== 4 =========================== Context accounted for 28.8% of measured spacetime 0x6BF83A: gdk_pixbuf_new (in /usr/lib/libgdk_pixbuf-2.0.so.0.400.9) 0x3A998998: (within /usr/lib/gtk-2.0/2.4.0/loaders/libpixbufloader-png.so) 0x6C2760: (within /usr/lib/libgdk_pixbuf-2.0.so.0.400.9) 0x6C285E: gdk_pixbuf_new_from_file (in /usr/lib/libgdk_pixbuf-2.0.so.0.400.9)

Called from: 27.8% : 0x804C1A3: load_scenario (swell-foop.c:463)

0.9% : 0x3E8095E: (within /usr/lib/libgnomeui-2.so.0.792.0)

and 1 other insignificant place

La première ligne indique que nous nous trouvons au quatrième niveau dans la profondeur de pile. Au-dessous, suit une liste des appels de fonctions menant de ce point à gdk_pixbuf_new. Enfin, vient une liste des fonctions situées au niveau immédiatement inférieur appelant ces fonctions. Bien entendu, les entrées pour les niveaux 1, 2 et 3 existent, mais le 4 est le premier niveau qui arrive directement jusqu’au code GDK de Swell Foop. À partir de cette liste, nous voyons instantanément que le problème se situe au niveau du code de `load_scenario`.

Maintenant que nous connaissons la partie du code qui utilise tout l’espace*temps mémoire, nous pouvons l’examiner et rechercher pourquoi. Il s’avère que `load_scenario` charge une image `pixbuf` à partir d’un fichier, puis ne libère jamais cette mémoire. Ayant identifié le code à problème, nous pouvons corriger.

# Modification des résultats

Réduire la consommation d’espace*temps mémoire est une bonne chose, mais il y a deux façons de faire et elles ne sont pas égales. Vous pouvez, soit réduire la quantité de mémoire allouée, soit réduire la durée d’allocation. Considérons un instant un système théorique avec uniquement deux processus en cours. Ces deux processus utilisent tous deux pratiquement la totalité de la mémoire RAM physique ; s’ils se superposent pleinement, alors le système va utiliser la partition de mémoire virtuelle et tout va ralentir. Il est évident que, si nous divisons par deux la quantité de mémoire utilisée par chaque processus, ils peuvent paisiblement coexister sans avoir besoin de l’utiliser. Si, au lieu de cela, nous divisons par deux les durées d’allocation mémoire, les deux programmes peuvent coexister, toujours sans virtualiser la mémoire, mais uniquement tant que leurs périodes d’utilisation de la mémoire haute ne se chevauchent pas. Réduire la quantité de mémoire allouée est donc préférable.

Hélas, les choix d’une optimisation sont également contraints par les besoins du programme. La taille des données de pixbuf de Swell Foop est déterminée par la taille des graphiques du jeu et ne peut pas être facilement réduite. Toutefois, la durée de chargement de l’image pixbuf en mémoire peut être réduite de manière drastique. L’image ci-dessus affiche l’analyse par Massif de l’application Swell Foop modifiée pour se débarrasser du `pixbuf` une fois que les images ont été chargées dans le serveur X.

## Sortie de Massif pour la version optimisée du programme Swell Foop.

L’espace*temps utilisé par `gdk_pixbuf_new` n’est maintenant plus qu’une fine bande qui pointe brièvement (c’est maintenant la seizième bande à partir du haut colorée en magenta). En prime, le pic de mémoire s’est affaissé de 200 Ko étant donné que la pointe de demande intervient avant les autres allocations mémoire. Si deux processus tels que celui-ci étaient lancés simultanément, les chances pour les pics d’utilisation mémoire coïncident, et en conséquence, les risques d’utilisation de la mémoire virtuelle, seraient très faibles.

Pouvons-nous faire mieux ? Un examen rapide du texte de sortie de Massif révèle que g_strdup serait maintenant le nouveau coupable.

Command: ./swell-foop

== 0 =========================== Heap allocation functions accounted for 87.6% of measured spacetime

Called from: 7.7% : 0x5A32A5: g_strdup (in /usr/lib/libglib-2.0.so.0.400.6)

7.6% : 0x43BC9F: (within /usr/lib/libgdk-x11-2.0.so.0.400.9)

6.9% : 0x510B3C: (within /usr/lib/libfreetype.so.6.3.7)

5.2% : 0x2A4A6B: __gconv_open (in /lib/tls/libc-2.3.3.so)

Si nous regardons de plus près, nous voyons qu’il est appelé en de très, très nombreux endroits.

== 1 =========================== Context accounted for 7.7% of measured spacetime 0x5A32A5: g_strdup (in /usr/lib/libglib-2.0.so.0.400.6)

Called from: 1.8% : 0x8BF606: gtk_icon_source_copy (in /usr/lib/libgtk-x11-2.0.so.0.400.9)

1.1% : 0x67AF6B: g_param_spec_internal (in /usr/lib/libgobject-2.0.so.0.400.6)

0.9% : 0x91FCFC: (within /usr/lib/libgtk-x11-2.0.so.0.400.9)

0.8% : 0x57EEBF: g_quark_from_string (in /usr/lib/libglib-2.0.so.0.400.6)

and 155 other insignificant places

Nous voilà maintenant confrontés à la diminution du rendement de nos efforts d’optimisation. Le graphique suggère une autre approche possible : les deux bandes `other` et `heap admin` sont relativement importantes. C’est la marque d’une multitude de petites allocations réalisées à divers endroits. Les éliminer est difficile, mais s’il est possible de les regrouper, les allocations unitaires seront plus grandes et le temps système du `heap admin` en sera réduit.

# Avertissements

Il faut faire attention à deux choses : d’une part, la quantité espace*temps est uniquement restituée en pourcentage ; il faut la rapporter à la taille globale du programme pour décider si la quantité de mémoire mérite une optimisation. L’axe vertical du graphique, gradué en kilo-octets, vous aidera à prendre cette décision.

D’autre part, Massif ne prend en compte que la mémoire utilisée par votre programme. Des ressources telles que les images sont stockées dans le serveur X et ne sont pas prise en considération par Massif. Dans l’exemple Swell Foop, nous n’avons fait que déplacer la consommation de mémoire des images pixbufs côté client vers les images pixmaps côté serveur. Même si nous avons triché, nous obtenons des gains de performance. Garder les données d’image dans le serveur X rend les routines graphiques rapides et supprime beaucoup de communications entre les processus. En outre, les pixmaps seront stockées dans un format graphique natif qui est souvent plus compact que le format RGBA 32 bits utilisé par gdk_pixbuf. Pour mesurer l’incidence de pixmaps et autres ressources X, utilisez le programme xrestop.