Babel : mesure de l'occupation mémoire

Introduction

Connaître la quantité de mémoire utilisée par une application n'est pas toujours facile. Sa connaissance est importante pour pouvoir déterminer les besoins lors de l'exécution. Sur une machine comme Babel, la mémoire est une ressource limitée. En effet, selon le mode d'exécution choisi, seuls 512 Mo, 1 Go ou 2 Go sont disponibles par processus.

Heureusement, vous avez accès à plusieurs outils sur Babel pour vous aider à mesurer l'occupation mémoire d'un code de calcul.

Attention  : si vous pensez avoir des problèmes dus à un manque de mémoire ou si vous pensez en disposer d'assez mais que votre application échoue lors d'allocations, il se peut que vous soyez touché par le phénomène de la fragmentation de la mémoire.

Commande ''size''

La commande size permet d'obtenir certaines informations concernant l'occupation mémoire d'un programme sans avoir à l'exécuter. Vous obtiendrez la taille des zones texte (instructions du programme), data (variables globales initialisées non-nulles) et BSS (variables globales non-initialisées ou nulles).

Attention : cette commande est incapable de vous donner la quantité de mémoire qui sera utilisée par des allocations dynamiques ou qui se trouve sur le stack.

Exemple d'utilisation :

votre_login@babel1:~> size mon_code
   text    data     bss     dec     hex filename
2197695  178460  618836 2994991  2db32f test

Utilisation de la bibliothèque libhpcidris

L'IDRIS a développé la bibliothèque libhpcidris permettant de déterminer l'utilisation de la mémoire d'une application en l'instrumentant.

Un module est disponible. Il permet de positionner les chemins pour la bibliothèque, les fichiers d'include et les modules Fortran.

module load libhpcidris

Voici un exemple de ce que vous obtenez sur la sortie standard pour l'utilisation du sous-programme HPCIDRIS_F03_MPI_memusage_print en Fortran ou HPCIDRIS_MPI_memusage_print en C :

-------------------------------------------------------------------------------------
                         MEMORY USAGE (libhpcidris version 3.1)
                       (C)2009-2011 Philippe WAUTELET, IDRIS-CNRS
-------------------------------------------------------------------------------------
                       |  Max (position)  |  Min (position)  |  Average  |    Sum
-------------------------------------------------------------------------------------
Used by application    |  266.9MiB(   63) |   26.6MiB(    0) |  146.8MiB |    9.2GiB
Available              |  483.6MiB(    1) |  241.1MiB(   63) |  361.5MiB |   22.6GiB
-------------------------------------------------------------------------------------
Text                   |    2.0MiB(    0) |    2.0MiB(    0) |    2.0MiB |   31.5MiB
Data                   |    1.1MiB(    0) |    1.1MiB(    0) |    1.1MiB |   72.2MiB
BSS                    |  633.0KiB(    0) |  633.0KiB(    0) |  633.0KiB |   39.6MiB
Stack                  |   44.8KiB(    0) |   44.8KiB(    0) |   44.8KiB |    2.8MiB
Heap                   |  262.9MiB(   63) |   22.6MiB(    0) |  142.7MiB |    8.9GiB
Dynamically allocated  |  253.9MiB(   63) |   13.6MiB(    0) |  133.7MiB |    8.4GiB
Allocated with mmap    |  939.6KiB(    0) |  837.5KiB(    1) |  839.1KiB |   52.4MiB
Reserved by kernel     |   16.0MiB(    0) |   16.0MiB(    0) |   16.0MiB |  256.0MiB
Shared                 |    8.0MiB(    0) |    8.0MiB(    0) |    8.0MiB |  128.0MiB
Max heap               |  262.9MiB(   63) |   22.6MiB(    0) |  142.7MiB |    8.9GiB
-------------------------------------------------------------------------------------

Mesures effectuées et leur signification

Mémoire utilisée par l'application

Donne une estimation de la mémoire utilisée par les différents processus de l'application exécutée. Elle correspond à la somme des zones text, data, BSS, stack et heap et tient compte de l'alignement sur les pages mémoire pour les 3 premières zones (la valeur sera donc un peu plus grande que la simple somme de tous ces champs).

Mémoire disponible

Estimation de la mémoire restant disponible pour chaque processus. Elle correspond à la mémoire totale à laquelle à accès un processus moins la mémoire utilisée.

Attention : la mémoire réellement disponible peut être plus grande car le heap peut contenir des trous.

La mémoire totale à laquelle à accès un processus n'est pas nécessairement la même pour tous les processus car une partie de l'espace mémoire de chaque noeud est réservée à des usages particuliers (zones Reserved by kernel, shared ou text).

Zones text, data et BSS

Les zones text, data et BSS correspondent respectivement à la mémoire utilisée pour stocker le code exécutable, les variables globales initialisées non-nulles et les variables globales nulles ou non-initialisées au démarrage de l'application.

Elles se trouvent dans des pages mémoire particulières (généralement d'une taille de 1 Mio).

Remarque : la zone text est partagée entre tous les processus d'un même noeud de calcul.

Stack

Le stack (pile) est la mémoire correspondant typiquement à celle allouée par les appels de fonctions ou sous-programmes et à leurs variables locales non persistantes.

Heap

Le heap (tas) est la mémoire correspondant typiquement aux allocations dynamique de mémoire (appels allocate en Fortran et calloc/malloc en C).

La taille donnée est la taille totale du heap. Or, celui-ci peut contenir des trous (phénomène de fragmentation mémoire). Il peut donc y avoir de la mémoire disponible à l'intérieur de cet espace.

Allocation dynamique

La mémoire allouée dynamiquement (Dynamically allocated) correspond aux appels allocate en Fortran et calloc/malloc en C. Elle est incluse dans la mémoire heap.

Il est à noter que la bibliothèque MPI en utilise. Si les valeurs obtenues vous semblent surestimées, la cause est probablement à chercher là.

Mémoire allouée avec mmap

Il s'agit de la mémoire allouée directement avec la fonction mmap.

La bibliothèque MPI semble en utiliser un petit peu.

Mémoire réservée par le noyau

La mémoire réservée par le noyau (Reserved by kernel) correspond à celle dont le système d'exploitation CNK à besoin. Celle-ci est toujours de 16 Mio par noeud de calcul (quel que soit le mode d'exécution).

Mémoire partagée

La mémoire partagée est un espace mémoire qui peut être utilisé avec la programmation de type shmem. Par défaut, elle est de 8 Mio et peut être modifiée avec la variable d'environnement BG_SHAREDMEMPOOLSIZE (à passer dans l'option -env de la commande mpirun, la valeur est donnée est méga-cotets).

La mémoire partagée est disponible pour tous les processus du noeud de calcul et n'existe pas en mode SMP.

Max heap

Valeur maximale de la taille du heap atteinte avant ou au moment de l'appel. Elle peut donc donner une idée de la mémoire maximale qui a été utilisée par l'application (attention, le stack n'est pas pris en compte ici).

Utilisation de libhpcidris dans un programme Fortran

Toutes les fonctionnalités de mesure de l'occupation mémoire sont disponibles par le chargement du module Fortran hpcidris. Dans votre programme Fortran, il suffit d'ajouter la ligne suivante partout où vous utilisez cette bibliothèque :

use hpcidris

Les sous-programmes disponibles sont les suivants :

  • HPCIDRIS_F03_MPI_memusage_print(comm,level) : affiche des informations sur l'occupation mémoire de tous les processus. Il prend 2 arguments : le communicateur à utiliser (c'est une communication collective et doit être appellé par tous ses membres) et le niveau de détails :
    • 0 : résumé avec valeurs max, min, moyenne et totale et les rangs des processus avec les valeurs max et min,
    • 1 : détaille l'occupation selon le type d'allocation (text, data, BSS, allocation dynamique,…),
    • 2 : détails pour chaque processus.
  • HPCIDRIS_F03_memusage_print : affiche l'utilisation de la mémoire du processus qui l'appelle (attention : ce sous-programme n'est pas MPI et chaque processus l'appellant affichera ses valeurs avec le risque d'obtenir des sorties mélangées)
  • HPCIDRIS_F03_memusage_get(mu) : récupère l'utilisation de la mémoire dans une structure de données HPCIDRIS_F03_memusage (voir plus loin)
  • HPCIDRIS_F03_memusage_print_at_exit() : affiche l'utilisation de la mémoire du processus qui l'appelle lorsque le processus se terminera normalement. Cet appel peut être fait n'importe quand dans votre application (attention : ce sous-programme n'est pas MPI et chaque processus l'appellant affichera ses valeurs avec le risque d'obtenir des sorties mélangées)

La structure de données HPCIDRIS_F03_memusage obtenue par le sous-programme HPCIDRIS_F03_memusage_get est détaillé ici :

type :: HPCIDRIS_F03_memusage
    character(len=5) :: mode
    integer(kind=8) :: totalmem
    integer(kind=8) :: text
    integer(kind=8) :: data
    integer(kind=8) :: bss
    integer(kind=8) :: stack
    integer(kind=8) :: heap
    integer(kind=8) :: mallocated
    integer(kind=8) :: mmaped
    integer(kind=8) :: reserved
    integer(kind=8) :: shared
    integer(kind=8) :: available
    integer(kind=8) :: totalused
    integer(kind=8) :: heapmax
    integer(kind=8) :: maxrss
end type HPCIDRIS_F03_memusage

Vous trouverez un exemple d'utilisation dans le répertoire /bglocal/pub/libhpcidris/3.1/examples.

Utilisation de libhpcidris dans un programme C

Toutes les fonctionnalités de mesure de l'occupation mémoire sont disponibles en incluant le fichier hpcidris.h ou hpcidris_mem.h. Dans votre programme C, il suffit d'ajouter la ligne suivante partout où vous utilisez cette bibliothèque :

#include ''hpcidris.h''

Les fonctions disponibles sont les suivantes :

  • HPCIDRIS_MPI_memusage_print(comm,level) : affiche des informations sur l'occupation mémoire de tous les processus. Elle prend 2 arguments : le communicateur à utiliser (c'est une communication collective et doit être appellé par tous ses membres) et le niveau de détails :
    • 0 : résumé avec valeurs max, min, moyenne et totale et les rangs des processus avec les valeurs max et min,
    • 1 : détaille l'occupation selon le type d'allocation (text, data, BSS, allocation dynamique,…),
    • 2 : détaillé pour chaque processus.
  • HPCIDRIS_memusage_print : affiche l'utilisation de la mémoire du processus qui l'appelle (attention : cette fonction n'est pas MPI et chaque processus l'appellant affichera ses valeurs avec le risque d'obtenir des sorties mélangées)
  • HPCIDRIS_memusage_get(mu) : récupère l'utilisation de la mémoire dans une structure de données struct HPCIDRIS_memusage (voir plus loin)
  • HPCIDRIS_memusage_print_at_exit() : affiche l'utilisation de la mémoire du processus qui l'appelle lorsque le processus se terminera normalement. Cet appel peut être fait n'importe quand dans votre application (attention : ce sous-programme n'est pas MPI et chaque processus l'appellant affichera ses valeurs avec le risque d'obtenir des sorties mélangées)

La structure de données struct HPCIDRIS_memusage obtenue par le sous-programme HPCIDRIS_memusage_get est détaillé ici :

struct HPCIDRIS_memusage {
  char mode[5];          Execution mode (VN, DUAL or SMP)

  long long totalmem;    Total memory

  long long text;        Text zone
  long long data;        Data
  long long bss;         BSS
  long long stack;       Stack
  long long heap;        Heap usage

  long long mallocated;  Dynamically allocated
  long long mmaped;      Allocated with mmap

  long long reserved;    Reserved by kernel
  long long shared;      Shared memory (shmem)

  long long available;   Available to the process
  long long totalused;   Used memory (without reserved by kernel)

  long long heapmax;     Maximum heap
  long long maxrss;      Max RSS
};