<?xml version="1.0" encoding="utf-8"?>
<chapter>
    <title>Použití aplikace <application>Massif</application> k profilování využití paměti v softwaru GNOME</title>
    
    <para>Tento článek popisuje profiler zásobníku <application>Massif</application> s aplikacemi GNOME. Popisuje jak získat, interpretovat a využít výstup z aplikace <application>Massif</application>. Jako příklad je použita hra <application>Stejný GNOME</application>.</para>

    <sect1 id="optimization-massif-TBL-intro">
        <title>Úvod</title>
        <para><application>Massif</application> je součástí sady nástrojů na profilování paměti <ulink type="http" url="http://valgrind.org/">valgrind</ulink>. Jeho účelem je poskytnout podrobný pohled na dynamické využití paměti během životního cyklu programu. Speciálně zaznamenává využití paměti haldy a zásobníku.</para>
        <para>Halda je oblast paměti, která se alokuje funkcemi jako je malloc. Podle požadavků se zvětšuje a obvykle je to největší paměťová oblast v programu. Zásobník je místo, ve kterém jsou uchovávána lokální data funkcí. To zahrnuje „automatické“ proměnné v C a návratovou adresu podprogramu. Zásobník je obvykle podstatně menší a méně aktivní než halda. Zásobníkem se výslovně zabývat nechceme, protože aplikace <application>Massif</application> s ním zachází stejně, jako by to byla jen další část haldy. Aplikace <application>Massif</application> rovněž poskytuje informace o tom, kolik paměti je využito ke správě haldy.</para>
        <para>Aplikace <application>Massif</application> vytváří dva výstupní soubory: grafický přehled v postskriptovém souboru a podrobný rozpis v textovém souboru.</para>
    </sect1>
    <sect1 id="optimization-massif-TBL-using-massif">
        <title>Používání aplikace <application>Massif</application> s GNOME</title>
        <para>Aplikace <application>Massif</application> má jen velmi málo přepínačů a pro mnoho programů je ani nepotřebujete. Když ale aplikace pro GNOME alokují paměť, mohou se zanořit  hluboko do glib nebo GTK, a proto je potřeba zvýšit počet úrovní zanoření zásobníku volání v Massif. Toho se dosáhne použitím přepínače --depth. Výchozí je 3, zvýšení na 5 zajistí dostatek zdrojů zásobníku volání pro váš kód. Jedna nebo více úrovní může být také žádoucí k poskytnutí vašeho kódu s nějakým kontextem. Jelikož s úrovní podrobností přichází rychlé zahlcení, je nejlepší začít s menší hodnotou přepínače depth a zvýšit ji jen v případě, kdy se ukáže nedostačující.</para>
        <para>Užitečné je také aplikaci <application>Massif</application> sdělit, které funkce v glib alokují paměť. Tím se odstraní z výstupní sestavy vrstvy volání funkcí, které nejsou nutné a získáte tak jasnější přehled, který kód alokuje paměť. Alokační funkce glib jsou g_malloc, g_malloc0, g_realloc, g_try_malloc a g_mem_chunk_alloc. Aplikaci Masiff to sdělíte přepínačem --alloc-fn.</para>
        <para>Váš příkazový řádek by pro předchozí měl vypadat nějak takto:</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 same-gnome
        </programlisting>
        <para><application>Same GNOME / Stejný GNOME</application> je program, který je použit jako ukázkový. Dopředu vás varujeme, že valgrind emuluje procesor, takže běh je <emphasis>velmi</emphasis> pomalý. K tomu ještě budete potřebovat velké množství paměti.</para>
    </sect1>
    <sect1 id="optimization-massif-TBL-interpreting-results">
        <title>Interpretace výsledků</title>
        <para>Grafický výstup aplikace <application>Massif</application> je pochopitelný i bez vysvětlování. Každý pás představuje paměť alokovanou jednou funkcí v průběhu času. Když zjistíte, který pás využívá nejvíce paměti, což je obvykle ten nejtlustší z nich nahoře, dohledejte si k němu podrobnosti v textovém souboru.</para>
        <para>Textový soubor je uspořádán do hierarchie oddílů, na začátku je seznam nejhorších uživatelů paměti v sestupném pořadí podle časoprostoru. Nasledují další oddíly, každý rozebírající výsledky do větších podrobností, jak se postupně zanořujete do zásobníku volání. Pro ilustraci použijeme výstup předchozího příkazu.</para>
        <figure id="optimization-massif-FIG-output-unoptimized">
            <title>Výstup z aplikace <application>Massif</application> pro neoptimalizovanou verzi prgramu <application>Stejný GNOME</application>.</title>
            <mediaobject>
                <imageobject>
                    <imagedata fileref="figures/massif-before.png" format="PNG"/>
                </imageobject>
            </mediaobject>
        </figure>
        <para><xref linkend="optimization-massif-FIG-output-unoptimized"/> ukazuje typický postskriptový výstup z aplikace <application>Massif</application>. Toto je výsledek, který byste získali, když odehrajete jednu hry <application>Stejný GNOME</application> (verze 2.8.0) a po té ji ukončíte. Postskriptový soubor bude mít název ve stylu <filename>massif.12345.ps</filename> a textový soubor bude nazván <filename>massif.12345.txt</filename>. Číslo uprostřed je ID procesu programu, který byl spuštěn. Pokud právě zkoušíte tento příklad, najdete dvě verze každého souboru s lehce odlišnými čísly, což je proto, že aplikace <application>Stejný GNOME</application> spouští druhý proces a aplikace <application>Massif</application> jej následuje. Tento druhý proces budeme ignorovat, spotřebovává jen velmi málo paměti.</para>
        <para>V horní části grafu vidíme velký žlutý pás označený gdk_pixbuf.new. Ten se zdá jako nejlepší kandidát na optimalizaci, ale nejdříve potřebujeme použít textový soubor, abychom našli co gdk_pixbuf_new volá. Začátek textového souboru vypadá nějak takto:</para>
        <programlisting>
Příkaz: ./same-gnome 

== 0 ===========================
Funkce pro alokaci haldy zodpovídají za 90,40% měřeného časoprostoru

Voláno z:
  28.8% : 0x6BF83A: gdk_pixbuf_new (v /usr/lib/libgdk_pixbuf-2.0.so.0.400.9)

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

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

    3.5% : 0x2A4A6B: __gconv_open (v /lib/tls/libc-2.3.3.so)
        </programlisting>
        <para>Řádek se znaky „=“ uvádí, jak hluboko v zásobníku sledování jsme zanoření, v tomto konkrétním případě jsem úplně nahoře. Následuje seznam nejnáročnějších uživatelů paměti v sestupném pořadí podle časoprostoru. Časoprostor je veličina představující jak moc paměti je použito a jako dlouho je použita. Odpovídá to ploše pásu v grafu. Tato část souboru nám říká, co již víme: nejvíce časoprostoru si zabrala funkce gdk_pisbuf_new. Abychom věděli, co gdk_pixbuf_new volá, musíme hledat v textovém souboru dále:</para>
        <programlisting>
== 4 ===========================
Kontext zodpovídá za 28,8% měřeného časového prostoru
  0x6BF83A: gdk_pixbuf_new (v /usr/lib/libgdk_pixbuf-2.0.so.0.400.9)
  0x3A998998: (uvnitř /usr/lib/gtk-2.0/2.4.0/loaders/libpixbufloader-png.so)
  0x6C2760: (uvnitř /usr/lib/libgdk_pixbuf-2.0.so.0.400.9)
  0x6C285E: gdk_pixbuf_new_from_file (v /usr/lib/libgdk_pixbuf-2.0.so.0.400.9)

Voláno z:
  27.8% : 0x804C1A3: load_scenario (same-gnome.c:463)

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

  a 1 dalšího nedůležitého místa
        </programlisting>
        <para>První řádek nám říká, že jsme zanoření do čtvrté úrovně v zásobníku. Níže je seznam volání funkcí, které vedou odtud na gdk_pixbuf_new. Nakonec je zde seznam funkcí, které jsou o další úroveň níže a volají tyto funkce. Jsou zde také samozřejmě  položky pro úrovně 1, 2 a 3, ale toto je první úroveň, která opravdu zasahuje přes kód GDK do kódu <application>Stejný GNOME</application>. Z tohoto seznamu můžeme ihned vidět, že problémovým kódem je load_scenario.</para>
        <para>Nyní, když víme, která část našeho kódu používá všechen časoprostor, můžeme se na ni podívat a zjistit proč. Ukáže se, že load_scenario načítá pixbuf ze souboru a potom nikdy neuvolní paměť. Tím mám zjištěno, kde je v kódu problém a muže jej začít opravovat.</para>
    </sect1>
    <sect1 id="optimization-massif-TBL-acting-on-results">
        <title>Využití výsledků</title>
        <para>Omezení spotřeby časoprostoru je dobré, ale existují dva způsoby jak jej zredukovat a nejsou rovnocenné. Buď můžete omezit množství alokované paměti nebo omezit dobu, po kterou je alokováno. Na chvíli si představte modelový systém, ve kterém běží jen dva procesy. Oba procesy využívají téměř všechnu fyzickou paměť a když ji vyčerpají celou, začne systém odkládat na disk a vše se zpomalí. Je zjevné, že když omezíme použití paměti rovnoměrně každým z obou procesů, mohou klidně běžet naráz, aniž by bylo potřeba odkládat na disk. Když místo toho omezíme čas, po který je paměť alokována, rovnoměrně u obou programů, mohou opět běžet v klidu současně, ale jen ve chvílích, kdy se nesejdou jejich největší požadavky na paměť. Proto je lepší omezit množství alokované paměti.</para>
        <para>Bohužel je volba optimalizace odvislá i od potřeb programu. Velikost dat pixbuf v aplikaci <application>Same GNOME</application> je dána velikostí grafiky hry a nelze ji jednoduše zmenšit. Může být ale značně omezeno množství času, po který je načtena v paměti. <xref linkend="optimization-massif-FIG-output-optimized"/> ukazuje analýzu aplikací <application>Massif</application> pro program <application>Stejný GNOME</application> po úpravě vedoucí k uvolnění pixbuf, jakmile byly obrázky načteny na X server.</para>
        <figure id="optimization-massif-FIG-output-optimized">
            <title>Výstup z aplikace <application>Massif</application> pro optimalizovanou verzi prgramu <application>Stejný GNOME</application>.</title>
            <mediaobject>
                <imageobject>
                    <imagedata fileref="figures/massif-after.png"/>
                </imageobject>
            </mediaobject>
        </figure>
        <para>Využití časoprostoru funkcí gdk_pixbuf_new představuje nyní tenčí pás, který jen zřídka povyskočí (je teď o šestnáct pozic níž a vyplněný purpurovou barvou). Jako bonus poklesl špičkový odběr paměti o 200 kB, protože špička se objeví před dalšími alokacemi paměti. Pokud běží naráz dva takové podobné procesy, je šance, že takové dvě paměťové špičky nastanou naráz, a tím vznikne riziko odkládání na disk, docela malá.</para>
        <para>Můžeme to udělat lépe? Rychlý průzkum textového výstupu <application>Massif</application> odhalí že: nový hlavní viník je g_strdup.</para>
        <programlisting>
Příkaz: ./same-gnome 

== 0 ===========================
Funkce pro alokaci haldy zodpovídají za 87,6% měřeného časoprostoru

Voláno z:
    7.7% : 0x5A32A5: g_strdup (v /usr/lib/libglib-2.0.so.0.400.6)

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

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

    5.2% : 0x2A4A6B: __gconv_open (v /lib/tls/libc-2.3.3.so)
        </programlisting>
        <para>Když se na to podíváme blíže, uvidíme, že je volán na mnoha, opravdu mnoha místech.</para>
        <programlisting>
== 1 ===========================
Kontext zodpovídá za 7,7% měřeného časového prostoru
  0x5A32A5: g_strdup (v /usr/lib/libglib-2.0.so.0.400.6)

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

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

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

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

  a 155 dalších nedůležitých míst
        </programlisting>
        <para>Nyní stojíme oproti klesající návratnost naší snahy o optimalizaci. Graf nám poradí jiný možný přístup: Pásy „ostatní“ a „správa haldy“ jsou docela velké. To nám říká, že je zde spousta malých alokací prováděných na různých místech. Odstranění něčeho takového může být obtížné, ale když je sloučíme do jediné alokace, může být větší a „správa haldy“ se tím zmenší.</para>
    </sect1>
    <sect1 id="optimization-massif-TBL-caveats">
        <title>Upozornění</title>
        <para>Je zde pár věcí, na které je třeba si dávat pozor: Zaprvé, časové úseky jsou uváděny jako procentuální část, musíte je vztáhnout na celkovou velikost programu, abyste mohli usoudit, zda množství paměti stojí za sledování. Dobrý je k tomu graf s hodnotami v kilobajtech na svislé ose.</para>
        <para>Zadruhé, aplikace <application>Massif</application> bere v úvahu jen paměť použitou vašim vlastním programem. Zdroje jako pixelové mapy jsou uložené na X serveru a aplikace <application>Massif</application> je nebere v úvahu. V ukázkovém programu <application>Stejný GNOME</application> máme právě jen přesuny spotřeby paměti ze strany klienta do pixelových map na straně serveru. Dokonce je tu nárůst výkonu, přestože tím vlastně švindlujeme. Uchováním obrazových dat na X serveru grafické rutiny zrychluje a odstraňuje spoustu meziprocesové komunikace. Mimo to jsou pixelové mapy uchované v nativním grafickém formátu, který je povětšinou více kompaktní než 32bitový RGBA formát používaný u gdk_pixbuf. K měření účinnosti pixelových map a ostatních zdrojů X serveru použijte program <ulink type="http" url="http://www.freedesktop.org/Software/xrestop">xrestop</ulink>.</para>
    </sect1>
</chapter>
