# Použití aplikace Massif k profilování využití paměti v softwaru GNOME

This article describes how to use the Massif heap profiler with GNOME applications. We describe how to invoke, interpret, and act on the output of Massif. The Swell Foop game is used as an example.

## Úvod

Massif je součástí sady nástrojů na profilování paměti valgrind. 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.

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 Massif s ním zachází stejně, jako by to byla jen další část haldy. Aplikace Massif rovněž poskytuje informace o tom, kolik paměti je využito ke správě haldy.

Aplikace Massif vytváří dva výstupní soubory: grafický přehled v postskriptovém souboru a podrobný rozpis v textovém souboru.

## Používání aplikace Massif s GNOME

Aplikace Massif 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í.

Užitečné je také aplikaci Massif 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.

Váš příkazový řádek by pro předchozí měl vypadat nějak takto:

    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 is the program we will be using as an example. Be warned that, since valgrind emulates the CPU, it will run very slowly. You will also need a lot of memory.

## Interpretace výsledků

Grafický výstup aplikace Massif 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.

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.

### Massif output for the unoptimized version of the Swell Foop program.

optimization-massif-FIG-output-unoptimized shows a typical postscript output from Massif. This is the result you would get from playing a single game of Swell Foop (version 2.8.0) and then quitting. The postscript file will have a name like `massif.12345.ps` and the text file will be called `massif.12345.txt`. The number in the middle is the process ID of the program that was examined. If you actually try this example you will find two versions of each file, with slightly different numbers, this is because Swell Foop starts a second process and Massif follows that too. We will ignore this second process, it consumes very little memory.

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:

    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)

Řá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:

    == 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

The first line tells us we are now four levels deep into the stack. Below it is a listing of the function calls that leads from here to gdk_pixbuf_new. Finally there is a list of functions that are at the next level down and call these functions. There are, of course, also entries for levels 1, 2, and 3, but this is the first level to reach right down through the GDK code to the Swell Foop code. From this listing, we can see instantly that the problem code is load_scenario.

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.

## Využití výsledků

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.

Unfortunately, the choice of optimization is also constrained by the needs of the program. The size of the pixbuf data in Swell Foop is determined by the size of the game's graphics and cannot be easily reduced. However, the amount of time it spends loaded into memory can be drastically reduced. optimization-massif-FIG-output-optimized shows the Massif analysis of Swell Foop after being altered to dispose of the pixbufs once the images have been loaded into the X server.

### Massif output for the optimized Swell Foop program.

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á.

Můžeme to udělat lépe? Rychlý průzkum textového výstupu Massif odhalí že: nový hlavní viník je g_strdup.

    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)

Když se na to podíváme blíže, uvidíme, že je volán na mnoha, opravdu mnoha místech.

    == 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

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ší.

## Upozornění

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.

Secondly, Massif only takes into account the memory used by your own program. Resources like pixmaps are stored in the X server and aren't considered by Massif. In the Swell Foop example we have actually only moved the memory consumption from client-side pixbufs to server-side pixmaps. Even though we cheated there are performance gains. Keeping the image data in the X server makes the graphics routines quicker and removes a lot of inter-process communication. Also, the pixmaps will be stored in a native graphics format which is often more compact than the 32-bit RGBA format used by gdk_pixbuf. To measure the effect of pixmaps, and other X resources use the xrestop program.