# Usar Massif para el Perfilado del uso de memoria en software de GNOME

Este artículo describe cómo usar el perfilador Massif con las aplicaciones de GNOME. Describe cómo invocar, interpretar y actuar sobre la salida de Massif. Se usa el juego Same GNOME como ejemplo.

## Introducción

Massif es un miembro de las herramientas de perfilado de memoria «heap» valgrind. Su propósito es proporcionar una vista detallada del uso de memoria dinámica durante la vida de un programa. Específicamente graba el uso de memoria del «heap» y de la pila.

El montículo («heap») es la región de memoria que está reservada con funciones como malloc. Crece bajo demanda y generalmente es la región de memoria más grande del programa. La pila es donde se almacenan todos los datos locales para las funciones. Incluye las variables «automáticas» de C y las direcciones de vuelta para las subrutinas. Generalmente la pila es mucho más pequeña y activa que el montículo. No se considerará explícitamente la pila ya que Massif la trata como si fuese otra parte del montículo. Massif también proporciona información acerca de cuánta memoria se usa para gestionar el montículo.

Massif produce dos archivos de salida: una vista general gráfica en un archivo postscript y un archivo de texto detallado.

## Usar Massif con GNOME

Massif tiene muy pocas opciones y para la mayoría de programas no se necesitan. No obstante, para las aplicaciones de GNOME donde la reserva de memoria puede estar debajo de glib o GTK, se debe aumentar el número de niveles debajo de la pilla de llamadas al que tiene descender Massif. Se consigue usando el parámetro «--depth». De forma predeterminada es 3; aumentarlo a 5 garantizará que la pilla de llamadas llega a su zona de código. Uno o dos niveles más pueden ser deseables para proporcionar algo de contexto al código. Ya que el nivel de detalle se convierte en insoportable, es mejor comenzar con un parámetro de profundidad pequeño e ir aumentándolo cuando parezca que es insuficiente.

También es útil decir a Massif qué funciones reservan memoria en GLib. Quita una capa de llamadas de funciones innecesarias de los informes y le proporciona una idea más clara de qué código está reservando memoria. Las funciones de reserva de memoria en GLib son g_malloc, g_malloc0, g_realloc, g_try_malloc, y g_mem_chunk_alloc. Use la opción «--alloc-fn» para pasarle la opción a Massif.

Su línea de comando debería parecerse a esta:

    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

Same GNOME es el programa que se usará como ejemplo. Tenga en cuenta que ya que valgrind emula la CPU se ejecutará muy despacio. También necesitará mucha memoria.

## Interpretar los resultados

La salida gráfica de Massif se explica por si misma. Cada banda representa la memoria que una función tiene reservada en el tiempo. Una vez que haya identificado las bandas que usan más memoria, generalmente las anchas en la parte superior, tendrá que consultar el archivo de texto para ver los detalles.

El archivo de texto se organiza como una jerarquía de secciones, en la parte superior hay una lista de los peores usuarios de memoria organizada en orden decreciente. Debajo de esta lista están las siguientes secciones, cada una mostrando los resultados en detalle según se procede en la pila de llamadas. Para ilustrar esto se usará la salida del comando anterior.

### La salida de Massif para la versión no optimizada del programa Same GNOME.

La muestra una salida postscript típica de Massif. Este es el resultado que obtendrá al jugar una partida simple de Same GNOME (versión 2.8.0) y después salir. El archivo postscript tendrá un nombre tipo `massif.12345.ps` y el archivo de texto se llamará `massif.12345.txt`. El número en el medio es el ID del proceso del programa que se examinó. Si prueba este ejemplo encontrará dos versiones de cada archivo con números ligeramente diferentes, esto es porque Same GNOME inicia un segundo proceso y Massif también lo sigue. Se ignorará el segundo proceso ya que consume muy poca memoria.

En la parte superior de la gráfica se ve una gran banda amarilla etiquetada como gdk_pixbuf_new. Parece ser un candidato ideal para la optimización, pero se necesitará usar el archivo de texto para encontrar qué está llamando a gdk_pixbuf_new. La parte superior del archivo de texto mostrará algo como lo siguiente:

    Command: ./same-gnome

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

La línea con los signos «=» indica a qué distancia de la traza de la pila estamos, en este caso al principio. Después lista los mayores usuarios de memoria en orden decreciente del espacio-tiempo. El espacio-tiempo es el producto de la cantidad de memoria usada y de la duración de su uso. Se corresponde con el área de las bandas en el gráfico. Esta parte del archivo proporciona información acerca de algo que ya se sabe: la mayoría del espacio-tiempo se dedica a gdk_pixbuf_new. Para buscar qué llamó a gdk_pixbuf_new necesitamos buscar más abajo en el archivo de texto:

    == 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 (same-gnome.c:463)

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

      and 1 other insignificant place

La primera línea dice que estamos a cuatro niveles de profundidad en la pila. Debajo es una lista de las llamadas de funciones que de aquí a gdk_pixbuf_new. Finalmente existe una lista de funciones que son el siguiente nivel por debajo y llaman a esas funciones. También existen, obviamente, entradas para los niveles 1, 2 y 3, pero este es el primer nivel que llega justo debajo del código GDK del código de Same GNOME. Fesde esta lista se puede ver instantáneamente que el problema del código es load_scenario.

Ahora que se sabe qué parte del código usa todo el espacio de tiempo se puede buscar el porqué. Resulta que load_scenario está cargando un pixbuf de un archivo y nunca libera esa memoria. Habiendo identificado el código del problema se puede comenzar a arreglarlo.

## Actuar sobre los resultados

Reducir el consumo de espacio-tiempo es bueno, pero existen otras dos formas de reducirlo y no son iguales. Puede reducir la cantidad de memoria reservada o reducir la cantidad de tiempo que está reservada. Considere por un momento un modelo de sistema con sólo dos procesadores. Ambos procesadores usan toda la memoria RAM física y si se solapan el sistema usará la memoria de intercambio y todo se ralentizará. Obviamente si se reduce el uso de memoria de cada proceso en un factor de dos, entonces pueden coexistir pacíficamente sin necesidad de usar la memoria de intercambio. Si en lugar de ello se reduce el tiempo que la memoria está reservada en un factor de dos, entonces los dos programas pueden coexistir, pero sólo siempre que sus periodos de máximo uso de memoria no se solapen. Por ello es mejor reducir la cantidad de memoria reservada.

Desafortunadamente la opción de la optimización también está restringida por las necesidades del programa. El tamaño de los datos de pixbuf en Same GNOME está determinado por el tamaño de los gráficos del juego y no se puede reducir fácilmente. No obstante, la cantidad de tiempo que está cargado en la memoria se puede reducir drásticamente. La muestra el análisis de Massif para Same GNOME después de alterar la disposición de los pixbuf una vez que se han cargado las imágenes en el servidor X.

### La salida de Massif para el programa optimizado Same GNOME.

Ahora el espacio-tiempo que usa gdk_pixbuf_new es una banda delgada que sólo tiene picos brevemente (ahora es la banda número dieciséis de magenta sombreado). Como ventaja, el pico de memoria ha disminuido 200 kB ya que el pico ocurre antes de que se reserve otra memoria. Si dos procesos como estos se tuviesen que ejecutar a la vez las posibilidades de que los picos de uso de memoria coincidiesen, y por ello el riesgo de usar la memoria de intercambio, serían bastante escasas.

¿Se puede hacer mejor? Un examen rápido de la salida de texto de Massif releva que g_strdup es el nuevo mayor problema.

    Command: ./same-gnome

    == 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 se mira con detenimiento se ve que se llama desde muchos, muchos sitios.

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

Ahora se encara la reducción de la salida para los esfuerzos de optimización. El gráfico sugiere otra posible aproximación: tanto la banda «other» como la «heap admin» son bastante largas. Esto quiere decir que existen muchas pequeñas reservas realizadas desde cierta variedad de sitios. Eliminarlas será difícil, pero si se pueden agrupar, entonces las reservas individuales pueden ser más grandes y la elevada banda «heap admin» puede reducirse.

## Advertencias

Existen un par de cosas cosas para tener en cuenta: Primeramente, sólo se informa del espacio de tiempo como porcentaje, tiene que compararlo con el tamaño total del programa para decidir si merece la pena perseguir la cantidad total de memoria. El gráfico, con su eje vertical de kilobytes, es bueno para ello.

Segundo, Massif sólo tiene en cuenta la memoria usada por su propio programa. Los recursos como los pixmaps se almacenan en el servidor X y Massif no los considerea. En el ejemplo de Same GNOME realmente sólo se ha movido el consumo de memoria de los pixbuf de la parte del cliente a los pixbuf de la parte del servidor. Aunque se haya hecho trampa se ha ganado en rendimiento. Mantener los datos de imágenes en el servidor X hace que las rutinas de gráficos sean más rápidas y elimina muchos procesos de comunicación interna. También, los pixmaps se almacenan en un formato de gráficos nativo que es más compacto que el formato de 32-bit RGBA usado por gdk_pixbuf. Para medir el efecto de los pixmaps y otros recursos X use el programa xrestop.