<?xml version="1.0" encoding="utf-8"?>
<chapter>
    <title>Utilisation de <application>Massif</application> pour profiler l'utilisation de la mémoire des logiciels GNOME</title>

    <para>Ce chapitre décrit comment utiliser le profileur du tas mémoire <application>Massif</application> avec les applications GNOME. Nous allons montrer comment appeler, interpréter et manipuler les sorties de <application>Massif</application>. Nous utiliserons le jeu <application>Swell Foop</application> en exemple.</para>

    <sect1 id="optimization-massif-TBL-intro">
        <title>Introduction</title>
        <para><application>Massif</application> fait partie de la suite <ulink type="http" url="http://valgrind.org/"><application>valgrind</application></ulink> d'outils de profilage de la mémoire. Sa finalité est de donner une vue détaillée de l'utilisation dynamique de la mémoire pendant la durée de vie d'un programme. Plus précisément, il enregistre l'utilisation de la mémoire du tas et de la pile.</para>
        <para>Le tas est une zone mémoire dans laquelle des fonctions comme <function>malloc</function> 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 <application>Massif</application> traite la pile comme faisant partie du tas. <application>Massif</application> fournit également des informations sur les quantités de mémoire utilisées pour la gestion du tas.</para>
        <para><application>Massif</application> produit deux fichiers de sortie : une vue d'ensemble graphique dans un fichier <literal>postscript</literal> et une répartition détaillée dans un fichier texte.</para>
    </sect1>
    <sect1 id="optimization-massif-TBL-using-massif">
        <title>Utilisation de <application>Massif</application> avec GNOME</title>
        <para><application>Massif</application> a très peu d'options ; beaucoup de programmes n'en ont pas besoin. Toutefois, pour les applications GNOME, où les allocations mémoire peuvent être profondément enfouies dans des fonctions de glib ou de GTK, le nombre de niveaux de la pile d'appel dans laquelle Massif descend doit être augmenté. L'option <option>--depth</option> paramètre cette profondeur. Par défaut, elle est de 3, l'augmenter à 5 garantit que la pile d'appel atteindra votre code. Un ou deux niveaux de plus peuvent également se révéler souhaitables pour analyser votre code dans un certain contexte. Étant donné que le niveau de détails devient vite envahissant, il est préférable de commencer avec un paramètre <option>depth</option> petit et de l'augmenter uniquement lorsqu'il devient évident que ce n'est pas suffisant.</para>
        <para>Il est également utile d'indiquer à <application>Massif</application> quelles sont les fonctions d'allocation mémoire dans <literal>glib</literal>. 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 <literal>glib</literal> sont : <function>g_malloc</function>, <function>g_malloc0</function>, <function>g_realloc</function>, <function>g_try_malloc</function> et <function>g_mem_chunk_alloc</function>. Utilisez l'option <option>--alloc-fn</option> pour les indiquer à <application>Massif</application>.</para>
        <para>Votre ligne de commande devrait par conséquent ressembler à quelque chose comme :</para>
        <programlisting>
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
        </programlisting>
        <para><application>Swell Foop</application> est le programme que nous utilisons en exemple. Soyez averti que, comme <application>valgrind</application> émule la CPU, il s'exécutera <emphasis>très</emphasis> lentement. Vous aurez également besoin de beaucoup de mémoire.</para>
    </sect1>
    <sect1 id="optimization-massif-TBL-interpreting-results">
        <title>Interprétation des résultats</title>
        <para>La sortie graphique de <application>Massif</application> est tout à fait explicite. Chaque bande représente la mémoire allouée par une fonction dans le temps. Après avoir repéré les bandes les plus goulues en mémoire, en général la plus épaisse en haut, vous devez consulter le fichier texte pour les détails.</para>
        <para>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.</para>
        <figure id="optimization-massif-FIG-output-unoptimized">
            <title>Sortie <application>Massif</application> pour la version non-optimisée de <application>Swell Foop</application>.</title>
            <mediaobject>
                <imageobject>
                    <imagedata fileref="figures/massif-before.png" format="PNG"/>
                </imageobject>
            </mediaobject>
        </figure>
        <para>La <xref linkend="optimization-massif-FIG-output-unoptimized"/> montre une sortie postscript typique de l'application <application>Massif</application>. Vous obtenez un résultat de ce type en jouant une seule partie de <application>Swell Foop</application> (version 2.8.0) puis en quittant. Le fichier postscript aura un nom du type <filename>massif.12345.ps</filename> et le fichier texte <filename>massif.12345.txt</filename>. Le nombre au milieu du nom est l'identifiant du processus correspondant au programme examiné. Si vous testez réellement cet exemple, vous trouverez deux versions de chacun de ces fichiers avec un numéro d'identifiant différent, car <application>Swell Foop</application> lance un deuxième processus que <application>Massif</application> analyse également. Nous ignorerons ce deuxième processus car il ne consomme que très peu de mémoire.</para>
        <para>En haut du graphique, est affichée une grande bande jaune étiquetée <literal>gdk_pixbuf_new</literal>. Elle semble être la candidate idéale pour une optimisation, mais nous avons besoin d'utiliser le fichier texte pour savoir qui appelle <literal>gdk_pixbuf_new</literal>. En tête du fichier texte, il y a quelque chose comme :</para>
        <programlisting>
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)
        </programlisting>
        <para>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 <literal>gdk_pixbuf_new</literal>. Pour savoir qui appelle <literal>gdk_pixbuf_new</literal>, il est nécessaire de rechercher plus bas dans le fichier texte :</para>
        <programlisting>
== 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
        </programlisting>
        <para>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 à <literal>gdk_pixbuf_new</literal>. 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 <application>Swell Foop</application>. À partir de cette liste, nous voyons instantanément que le problème se situe au niveau du code de <function>load_scenario</function>.</para>
        <para>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 <literal>load_scenario</literal> charge une image <literal>pixbuf</literal> à partir d'un fichier, puis ne libère jamais cette mémoire. Ayant identifié le code à problème, nous pouvons corriger.</para>
    </sect1>
    <sect1 id="optimization-massif-TBL-acting-on-results">
        <title>Modification des résultats</title>
        <para>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.</para>
        <para>Hélas, les choix d'une optimisation sont également contraints par les besoins du programme. La taille des données de l'image <literal>pixbuf</literal> de <application>Swell Foop</application> 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 <literal>pixbuf</literal> en mémoire peut être réduite de manière drastique. <xref linkend="optimization-massif-FIG-output-optimized"/> affiche l'analyse par <application>Massif</application> de l'application <application>Swell Foop</application> modifiée pour se débarrasser du <literal>pixbuf</literal> une fois que les images ont été chargées dans le serveur X.</para>
        <figure id="optimization-massif-FIG-output-optimized">
            <title>Sortie <application>Massif</application> pour <application>Swell Foop</application> optimisé.</title>
            <mediaobject>
                <imageobject>
                    <imagedata fileref="figures/massif-after.png"/>
                </imageobject>
            </mediaobject>
        </figure>
        <para>L'espace*temps utilisé par <literal>gdk_pixbuf_new</literal> 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.</para>
        <para>Pouvons-nous faire mieux ? Un examen rapide de la sortie texte de <application>Massif</application> révèle que <function>g_strdup</function> serait maintenant le nouveau coupable.</para>
        <programlisting>
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)
        </programlisting>
        <para>Si nous regardons de plus près, nous voyons qu'il est appelé en de très, très nombreux endroits.</para>
        <programlisting>
== 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
        </programlisting>
        <para>Nous voila maintenant confrontés à la diminution du rendement de nos efforts d'optimisation. Le graphique suggère une autre approche possible : les deux bandes <literal>other</literal> et <literal>heap admin</literal> 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 <literal>heap admin</literal> en sera réduit.</para>
    </sect1>
    <sect1 id="optimization-massif-TBL-caveats">
        <title>Avertissements</title>
        <para>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.</para>
        <para>D'autre part, <application>Massif</application> ne prend en compte que la mémoire utilisée par votre propre programme. Des ressources telles que les images sont stockées dans le serveur X et <application>Massif</application> ne les prend pas en considération. Dans l'exemple <application>Swell Foop</application> nous avons en réalité seulement déplacé la consommation mémoire des images <literal>pixbufs</literal> côté client vers les images <literal>pixmap</literal> côté serveur. Même si c'est une tromperie, nous obtenons une amélioration des performances. Garder les données de l'image dans le serveur X accélère les routines graphiques et supprime un certain nombre de communications entre processus. Également, les images sont stockées au format graphique natif qui est souvent plus compact que le format RGBA 32 bits utilisé par <literal>gdk_pixbuf</literal>. Pour mesurer l'incidence des images et autres ressources X, utilisez le programme <ulink type="http" url="http://www.freedesktop.org/Software/xrestop"><literal>xrestop</literal></ulink>.</para>
    </sect1>
</chapter>
