Turing : outils de mesure de la mémoire

Avant propos

Sur Turing, la mémoire est limitée à 16 Gio par nœud. Cette limite est à diviser par la valeur mise dans le paramètre --ranks-per-node. Ainsi si vous lancez 32 taches MPI par nœud, la mémoire disponible pour chaque processus est 512 Mio maximum.

Module idrmemmpi

La bibliothèque idrmemmpi permet d'afficher la consommation maximum de la mémoire d'un programme MPI pendant la durée de son exécution. Cet affichage a lieu au moment où l’exécution s'arrête . Il est indispensable de charger le module idrmemmpi avant la compilation de votre programme :

$ module load idrmemmpi
$ mpixlf90_r main.f90 -o main

Aucun changement n'est nécessaire dans le script de soumission. On obtient sur la sortie standard à la fin de l’exécution le temps elapsed, le maximum de consommation de mémoire (HeapMax) ainsi que le rang du processus sur lequel ce maximum est atteint.

IdrisMemMPI v1.4 : elapsed = 0.32 s, HeapMax = 497 mB on rank 1

Si vous souhaitez afficher la consommation courante à un endroit précis, il suffit d'insérer un appel à la routine idr_print_mem

  CALL idr_print_mem()

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 4.0)
                       (C)2009-2013 Philippe WAUTELET, IDRIS-CNRS
-------------------------------------------------------------------------------------
                       |  Max (position)  |  Min (position)  |  Average  |    Sum
-------------------------------------------------------------------------------------
Used by application    |  800.3MiB( 1023) |   19.1MiB(    1) |  410.1MiB |  410.1GiB
Available              | 1011.0MiB(    2) |  147.2MiB( 1008) |  613.1MiB |  613.1GiB
-------------------------------------------------------------------------------------
Text                   |    6.3MiB(    0) |    6.3MiB(    0) |    6.3MiB |  401.1MiB
Data                   |  946.1KiB(    0) |  946.1KiB(    0) |  946.1KiB |  946.1MiB
BSS                    |  901.9KiB(    0) |  901.9KiB(    0) |  901.9KiB |  901.9MiB
Stack                  |   19.4KiB(    0) |   19.4KiB(    0) |   19.4KiB |   19.4MiB
Heap                   |  792.3MiB( 1023) |   11.0MiB(    1) |  402.0MiB |  402.0GiB
Reserved by kernel     |   16.0MiB(    0) |   16.0MiB(    0) |   16.0MiB |    1.0GiB
Shared                 |   64.0MiB(    0) |   64.0MiB(    0) |   64.0MiB |    4.0GiB
Max heap               |  796.3MiB( 1023) |   15.0MiB(    1) |  406.0MiB |  406.0GiB
-------------------------------------------------------------------------------------

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.

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.

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

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

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 64 Mio et peut être modifiée avec la variable d'environnement BG_SHAREDMEMSIZE (à passer dans l'option –envs de la commande runjob, la valeur est donnée est méga-octets).

La mémoire partagée est disponible pour tous les processus du noeud de calcul.

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, heap…),
    • 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
    integer         :: ppn
    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) :: reserved
    integer(kind=8) :: persist
    integer(kind=8) :: guard
    integer(kind=8) :: shared
    integer(kind=8) :: stackavail
    integer(kind=8) :: heapavail
    integer(kind=8) :: totalused
    integer(kind=8) :: heapmax
    !integer(kind=8) :: maxrss
  end type HPCIDRIS_F03_memusage

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, heap…),
    • 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 la fonction HPCIDRIS_memusage_get est détaillé ici :

struct HPCIDRIS_memusage {
  int ppn;              // Processes per node

  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 reserved;   // Reserved by kernel
  long long persist;    // Persistent memory
  long long guard;      // Heap guardpage
  long long shared;     // Shared memory (shmem)

  long long stackavail; // Stack available to the process
  long long heapavail;  // Heap available to the process
  long long totalused;  // Used memory (without reserved by kernel)

  long long heapmax;    // Maximum heap
};