Turing : mesures de performances avec libhpcidris

Attention : cette fonctionalité n'est disponible que depuis la version 5.0 de libhpcidris.

Introduction

L'IDRIS a développé la bibliothèque libhpcidris permettant de mesurer les performances d'une application telles que le nombre d'opérations en virgule flottante (FLOP/s) ou sur les entiers, certains débits mémoires, réseaux et entrées-sorties en l'instrumentant. Cette bibliothèque réalise ces mesures en relevant les compteurs matériels présents sur chaque noeud de calcul.

Voici un exemple de ce que vous obtenez sur la sortie standard pour l'utilisation des sous-programmes HPCIDRIS_F03_MPI_hardcount_start et HPCIDRIS_F03_MPI_hardcount_stop en Fortran ou HPCIDRIS_MPI_hardcount_start et HPCIDRIS_MPI_hardcount_stop en C avec un niveau de détails de 0 (HPCIDRIS_LEVEL_DEFAULT) :

-----------------------------------------------------------------------
                PERFORMANCE (libhpcidris version 5.0)
              (C)2009-2014 Philippe WAUTELET, IDRIS-CNRS
-----------------------------------------------------------------------
 Elapsed time: 86.877202s (on process 0)
 Processes: 8192 (16 per node), threads per process: 1
 Reserved nodes (bg_size): 512 (IO nodes: 8)
 Torus dimensions: 4x4x4x4x2, is a torus: 1 1 1 1 1
-------------------------------------------------------------------------------------------
                |        Sum       |          Average         |      Volume    |   %peak  |
-------------------------------------------------------------------------------------------
FLOP/s total    |    2.622TFLOP/s  |  320.069MFLOP/s  /thread |  227.780TFLOP  |    2.50% |
FLOP/s FPU      |    1.562TFLOP/s  |  190.708MFLOP/s  /thread |  135.719TFLOP  |    5.96% |
 NoFMA FPU      |  804.623GFLOP/s  |   98.221MFLOP/s  /thread |   69.899TFLOP  |    6.14% |
 FMA FPU        |  757.658GFLOP/s  |   92.488MFLOP/s  /thread |   65.819TFLOP  |    2.89% |
FLOP/s QPU      |    1.060TFLOP/s  |  129.361MFLOP/s  /thread |   92.061TFLOP  |    1.01% |
 NoFMA QPU      |  531.613GFLOP/s  |   64.894MFLOP/s  /thread |   46.182TFLOP  |    1.01% |
 FMA QPU        |  528.111GFLOP/s  |   64.467MFLOP/s  /thread |   45.878TFLOP  |    0.50% |
FLoat inst/s    |    1.465TFLINS/s |  178.856MFLINS/s /thread |  127.284TFLINS |   11.18% |
Integer op/s    |  897.173GINTOP/s |  109.518MINTOP/s /thread |   77.940TINTOP |    6.84% |
Instructions/s  |    4.957TINST/s  |  605.120MINST/s  /thread |  430.638TINST  |   18.91% |
---------------------------------------------------------------------------------------------
L2 read         |    5.371TiB/s    |  687.446MiB/s    /thread   |   466.562TiB   |    2.82% |
DDR read        |    3.004TiB/s    |    6.008GiB/s    /node     |   260.983TiB   |   15.12% |
DDR write       |    2.727TiB/s    |    5.454GiB/s    /node     |   236.901TiB   |   13.73% |
Network sent    |   20.062GiB/s    |   40.124MiB/s    /node     |     1.702TiB   |    0.21% |
IO reads        |   89.249MiB/s    |   11.156MiB/s    /ION      |     7.571GiB   |    0.29% |
IO writes       |    1.524GiB/s    |  195.052MiB/s    /ION      |   132.379GiB   |    5.11% |
---------------------------------------------------------------------------------------------

Mesures effectuées et leur signification

Attention : l'ensemble des valeurs données dans cette rubrique sont très subjectives et ne doivent servir qu'à vous donner des ordres de grandeur. Selon le type d'application, le jeu de données…, celles-ci peuvent s'éloigner notablement de ce qui est proposé ici.

Nombre d'opérations en virgule flottante par seconde (FLOP/s)

Le nombre d'opérations en virgule flottante est une mesure de l'utilisation des unités de calculs sur les nombres réels de la machine. Les opérations comptées ici sont les additions, soustractions, multiplications, divisions et quelques autres opérations plus spécifiques (par exemple les racines carrées ou les valeurs absolues).

Comme sur toutes les machines à processeurs scalaires, la plupart des applications mêmes bien optimisées ne sont capables d'utiliser qu'une fraction de la puissance crête théorique. Une application tournant entre 5 et 10% de cette valeur est généralement considérée comme ayant une performance très satisfaisante. Attention, cela dépend très fortement du type d'application, de la taille du problème étudié,…

Les valeurs sont séparées en quatre :

  • NoFMA FPU pour les opérations non FMA (Fused Multiply Add) et non vectorielles de calcul en virgule flottante ;
  • FMA FPU pour les opérations FMA (Fused Multiply Add) et non vectorielles de calcul en virgule flottante ;
  • NoFMA QPU pour les opérations non FMA (Fused Multiply Add) et utilisant l'unité vectorielle de calcul en virgule flottante ;
  • FMA QPU pour les opérations FMA (Fused Multiply Add) et utilisant l'unité vectorielle de calcul en virgule flottante.

Le champ FLOP/s FPU correspond à la somme de NoFMA FPU et de FMA FPU ; FLOP/s QPU est la somme de NoFMA QPU et de FMA QPU; et FLOP/s total est la somme de FLOP/s FPU et de FLOP/s QPU.

La puissance crête de la machine est calculée en tenant compte de l'unité de calcul vectoriel travaillant sur 4 flottants double précision en parallèle. Si elle n'est pas utilisée avec des opérations vectorielles, la puissance de calcul pouvant être atteinte est divisée par 4.

De plus, les coeurs de calcul sont capables de réaliser à chaque cycle d'horloge et par unité de calcul en virgule flottante deux opérations flottantes si il s'agit d'une addition (ou soustraction) couplée à une multiplication (a = b + c*d, opération appellée FMA pour Fused Multiply-Add par IBM). Si votre application ne réalise pas ce type d'opérations, la performance maximale sera encore divisée par 2.

Nombre d'instructions en virgule flottante par seconde (FLoat inst/s)

Cette mesure donne le nombre d'instructions en virgule flottante exécutées sur la machine par seconde. Une instruction peut représenter plusieurs opérations selon le type d'instruction (1 opération pour une instruction non vectorielle et non FMA, 2 pour une instruction non vectorielle et FMA, 4 pour une instruction vectorielle et non FMA et 8 pour une instruction vectorielle et FMA).

Le maximum théorique est d'une instruction par cycle et par coeur.

Nombre d'opérations sur des entiers (Integer op/s)

Cette mesure donne le nombre d'instructions (ou opérations, l'unité étant non vectorielle) sur des entiers exécutées sur la machine par seconde.

Le maximum théorique est d'une instruction par cycle et par coeur. Il est très difficile à atteindre car, par exemple, les instructions de lecture et écriture dans la mémoire partagent aussi ces ressources (1 instruction par cycle). Par exemple, la mise à jour d'un tableau d'entier a[i]=a[i]+1 implique 1 lecture et 1 écriture par opération. En dehors des considérations des accès mémoire, on ne pourra donc, pour ce cas, dépasser 1/3 de la crête pour les opérations sur les entiers.

Nombre d'instructions (Instructions/s)

Cette mesure donne le nombre d'instructions de tout type exécutées sur la machine par seconde.

Le maximum théorique est de 2 instructions par cycle et par coeur. A chaque cycle, un coeur est capable d'exécuter à la fois jusqu'à une instruction en virgule flottante et une autre instruction (opération sur des entiers, lecture ou écriture mémoire ou autre). Par contre, à chaque cycle, un processus (ou thread) donné est limité à une seule instruction. Pour exécuter plus d'une opération par cycle, il est donc nécessaire de surcharger les coeurs avec plusieurs processus (ou threads). Sur Blue Gene/Q, il est ainsi possibler d'exécuter jusqu'à 4 instances hardware simultanées.

Débits en lecture depuis le cache L2 (L2 read)

Le débit en lecture depuis le cache L2 est disponible. Il permet de mesurer la pression s'exerçant sur la mémoire cache de niveau 2 (il existe aussi un niveau 1 de mémoire cache dont les débits ne sont pas mesurables). Une valeur élevée (de plus de 50% de la crête) indique que votre application sollicite très fortement la mémoire et est probablement limitée par ces débits si le nombre de FLOP/s est loin de la crête.

D'un autre côté, une valeur faible (inférieure à 10%) associée à un nombre de FLOP/s faible également peut indiquer que les coeurs de calcul perdent beaucoup de temps à attendre les données ou à les écrire. Cela peut être le cas si vous faites beaucoup d'accès aléatoires à la mémoire ou si vous effectuez beaucoup d'opérations coûteuses (divisions, racines carrées…).

Il ne faut pas non plus oublier que les débits mesurés prennent en compte non seulement les variables en virgule flottante, mais également les variables entières, les requêtes sur les instructions machines…

Remarque : les mesures de débits en écriture dans le L2 ou en lecture et en écriture dans le L1 ne sont pas disponibles sur Turing.

Débits avec la mémoire centrale (DDR read et DDR write)

Les mêmes remarques que pour les débits sur la mémoire cache de niveau 2 peuvent être faites.

Il faut également savoir que les données dans la mémoire centrale sont lues ou écrites par blocs de 128 octets (taille d'une ligne de cache de niveau L2). Même si vous n'avez besoin d'accéder qu'à un seul élément dans un tableau, vous serez obligé de charger la totalité de la ligne de cache correspondante.

Il est également important de comparer les débits avec le cache L2 et avec la mémoire centrale. Si ils sont proches, cela signifie que les données dans les caches sont très peu réutilisées et les performances (en terme de FLOP/S) seront probablement assez mauvaises. A l'opposé, si le débit L2 est beaucoup plus grand, il y a une bonne réutilisation des données dans les caches et les performances ne devraient pas être trop mauvaises.

La documentation sur l'optimisation séquentielle explique le fonctionnement des caches et comment en tirer parti.

Débit en envoi sur le réseau tore 5D (Network sent)

Le débit en envoi sur le réseau tore 5D correspond à la somme des données injectées par le noeud de calcul sur le réseau en tore 5D dans les 10 directions existantes. Chaque direction à un débit maximum de 2 Go/s pour un total de 20 Go/s.

Ces mesures incluent les données envoyées par les processus de chaque noeud de calcul, mais aussi le trafic pouvant les traverser. Un message transitant par plusieurs noeuds de calcul sera donc compté plusieurs fois. Elles incluent les en-têtes propres au système pour assurer le routage de ces données, ainsi que les en-têtes MPI.

Le débit en réception n'est pas disponible.

Débits d'entrées/sorties (IO writes et IO reads)

Les débits en écriture et en lecture sont donnés. Ceux-ci incluent tous les I/O sur des fichiers, sur les entrées-sorties et erreurs standards, ainsi que les données transférées via des sockets (peu d'applications Blue Gene/Q utilisent ces derniers).

Ces compteurs incluent également les en-têtes système (un en-tête de 32 octets par opération ou bloc de maximum 512 octets de données applicatives) et les opérations de contrôle (si vous faites des écritures, vous constaterez également un peu de trafic dans le sens des lectures et inversément).

La valeur crête est donnée en fonction du nombre de noeuds d'I/O disponibles (un tous les 64 noeuds de calcul) et en considérant le débit réseau maximal de chaque bloc de 64 noeud de calcul vers son noeud d'I/O (2 liens, chacun à 2Go/s). Tous les tests réalisés jusqu'à présent ont montré que le débit vers les systèmes de fichiers ne pouvait pas dépasser 3 Gio/s par noeud d'I/O. N'oubliez pas non plus que vous êtes limité par le débit maximum des serveurs de fichiers (50 Go/s pour les WORKDIR et TMPDIR) et que vous n'êtes pas seul sur la machine.

Utilisation

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

Utilisation de libhpcidris dans un programme Fortran

Toutes les fonctionnalités de mesures de performances sont disponibles par le chargement du module Fortran hpcidris (ou hpcidris_hardcount). 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_hardcount_start(sync) : démarre la mesure de performances.
  • HPCIDRIS_F03_MPI_hardcount_stop(thrdbyproc,level,sync) : arrête et affiche les mesures de performances sur l'ensemble des processus de l'application. L'argument thrdbyproc doit être égal au nombre de threads exécutés par tâche MPI (1 pour une application MPI pure). Le niveau de détails de l'affichage est choisi avec l'argument level :
    • HPCIDRIS_LEVEL_DEFAULT : résumé pour l'ensemble des noeuds de calcul ;
    • HPCIDRIS_LEVEL_ALLPRC : sorties détaillées pour tous les noeuds de calcul ;
    • HPCIDRIS_LEVEL_MISSES : donne des informations sur les cache miss (attention : valeurs assez difficiles à interpréter).
  • HPCIDRIS_F03_MPI_hardcount_init() : initialise les structures de libhpcidris. Il n'est pas nécessaire de faire appel à ce sous-programme avant de faire un appel à HPCIDRIS_F03_MPI_hardcount_start car il sera appelé automatiquement si nécessaire.
  • HPCIDRIS_F03_MPI_hardcount_finalize() : libère les ressources utilisées par la bibliothèque libhpcidris. Il n'est pas nécessaire de l'appeler en fin de programme. Cet appel ne peut être fait qu'une seule fois pendant toute l'exécution de votre programme.

Ces sous-programmes sont collectifs et doivent être appellés par l'ensemble des processus de l'application (communicateur MPI_COMM_WORLD).

L'argument sync permet de définir la politique de synchronisation des processus juste avant de commencer la mesure :

  • HPCIDRIS_SYNC_DEFAULT pour la valeur par défaut (synchronisation forcée pour HPCIDRIS_F03_MPI_hardcount_start et pas de synchronisation pour HPCIDRIS_F03_MPI_hardcount_stop) ;
  • HPCIDRIS_SYNC_YES pour qu'il y ait synchronisation entre tous les processus ;
  • HPCIDRIS_SYNC_NO pour qu'il n'y en ait pas.

Utilisation de libhpcidris dans un programme C

Toutes les fonctionnalités de mesure de performances sont disponibles en incluant le fichier hpcidris.h (ou hpcidris_hardcount.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_hardcount_start(sync) : démarre la mesure de performances.
  • HPCIDRIS_MPI_hardcount_stop(thrdbyproc,level,sync) : arrête et affiche les mesures de performances sur l'ensemble des processus de l'application. L'argument thrdbyproc doit être égal au nombre de threads exécutés par tâche MPI (1 pour une application MPI pure). Le niveau de détails de l'affichage est choisi avec l'argument level :
    • HPCIDRIS_LEVEL_DEFAULT : résumé pour l'ensemble des noeuds de calcul ;
    • HPCIDRIS_LEVEL_ALLPRC : sorties détaillées pour tous les noeuds de calcul ;
    • HPCIDRIS_LEVEL_MISSES : donne des informations sur les cache miss (attention : valeurs assez difficiles à interpréter).
  • HPCIDRIS_MPI_hardcount_init() : initialise les structures de libhpcidris. Il n'est pas nécessaire de faire appel à cette fonction avant de faire un appel à HPCIDRIS_F03_MPI_hardcount_start car il sera appelé automatiquement si nécessaire.
  • HPCIDRIS_MPI_hardcount_finalize() : libère les ressources utilisées par la bibliothèque libhpcidris. Il n'est pas nécessaire de l'appeler en fin de programme. Cet appel ne peut être fait qu'une seule fois pendant toute l'exécution de votre programme.

Ces fonctions sont collectives et doivent être appellées par l'ensemble des processus de l'application (communicateur MPI_COMM_WORLD).

L'argument sync permet de définir la politique de synchronisation des processus juste avant de commencer la mesure :

  • HPCIDRIS_SYNC_DEFAULT pour la valeur par défaut (synchronisation forcée pour HPCIDRIS_F03_MPI_hardcount_start et pas de synchronisation pour HPCIDRIS_F03_MPI_hardcount_stop) ;
  • HPCIDRIS_SYNC_YES pour qu'il y ait synchronisation entre tous les processus ;
  • HPCIDRIS_SYNC_NO pour qu'il n'y en ait pas.

Remarques

  • Les appels engendrent des surcoûts limités mais non nuls (environ 2 microsecondes par appel). L'utilisation de ces fonctionalités n'est donc pas adaptée à des mesures très fines car la durée mesurée ne sera pas assez précise (les volumes mesurés seront néanmoins corrects). De plus, l'affichage n'est pas instantané et entraîne des communications MPI entre certains processus (1 par noeud). Une légère désynchronisation des processus peut alors apparaître. Les mesures ayant été effectuées avant cette opération, l'affichage ne perturbe pas les mesures.
  • Tous les appels sont des appels collectifs au sens MPI et doivent être réalisés par tous les processus de l'application (communicateur MPI_COMM_WORLD).
  • Pour des raison inhérentes à l'architecture de la machine, les mesures de performance ne peuvent être réalisées à un instant donné que pour maximum un thread ou processus par coeur. Si votre application surcharge les coeurs (plusieurs processus ou threads par coeur), les mesures seront extrapolées. Vous obtiendrez alors un message du type : WARNING: values per thread are extrapolated (only 1 thread per core is instrumented)
  • Toute mesure commencée doit être arrêtée une et une seule fois.