Babel : mesures de performances avec 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), les débits mémoires ou les débits réseaux en l'instrumentant. Cette bibliothèque réalise ces mesures en relevant les compteurs matériels présents dans 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 :

-----------------------------------------------------------------------
                PERFORMANCE (libhpcidris version 3.1)
              (C)2009-2010 Philippe WAUTELET, IDRIS-CNRS
-----------------------------------------------------------------------
 Elapsed time: 158.104840s (on node 0)
 4096 processes with 1 thread(s) in VN mode
 1024 compute nodes, 16 IO nodes
 WARNING: values extrapolated on 1024 compute nodes
---------------------------------------------------------------------------------------
                |       Sum       |         Average         |     Volume    |  %peak  |
---------------------------------------------------------------------------------------
FLOP/s 1XFPU    |  216.935GFLOP/s |   52.963MFLOP/s by core |   34.298TFLOP |   3.12% |
FLOP/s 2XFPU    |    1.159TFLOP/s |  283.038MFLOP/s by core |  183.291TFLOP |   8.32% |
FLOP/s (total)  |    1.376TFLOP/s |  336.001MFLOP/s by core |  217.589TFLOP |   9.88% |
---------------------------------------------------------------------------------------
L1 read         |    4.686TiB/s   |    1.172GiB/s   by core |  740.882TiB   |  18.50% |
L1 write        |    2.912TiB/s   |  745.398MiB/s   by core |  460.345TiB   |  11.49% |
DDR read        |    2.312TiB/s   |    2.312GiB/s   by node |  365.504TiB   |  18.25% |
DDR write       |    1.503TiB/s   |    1.503GiB/s   by node |  237.597TiB   |  11.86% |
Torus send      |    9.540GiB/s   |    9.540MiB/s   by node |    1.473TiB   |   0.39% |
Collective send |  199.163KiB/s   |      199B/s     by node |   30.750MiB   |   0.00% |
Collective recv |  199.163KiB/s   |      199B/s     by node |   30.750MiB   |   0.00% |
IO write        |    1.780KiB/s   |        1B/s     by node |  281.500KiB   |   0.00% |
IO read         |      911B/s     |        0B/s     by node |  140.750KiB   |   0.00% |
---------------------------------------------------------------------------------------

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ême 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 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 deux :

  • FLOP/s 1XFPU pour les opérations n'impliquant pas l'unité double de calcul en virgule flottante ;
  • FLOP/s 2XFPU pour les opérations utilisant l'unité double de calcul en virgule flottante.

La puissance crête de la machine est calculée en tenant compte de cette double unité de calcul. Si elle n'est pas utilisée, vous ne pourrez atteindre au mieux que la moitié de cette valeur.

L'utilisation de l'unité double de calcul en virgule flottante est décrite dans cette page.

Il faut également savoir que 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.

Débits entre les coeurs de calcul et le cache L1 (L1 read et L1 write)

Les débits en lecture et en écriture entre les coeurs de calcul et le cache L1 sont disponibles. Ils permettent de mesurer la pression s'exercant sur la mémoire cache. 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,… Par contre, les requêtes sur les instructions machines ne sont pas intégrées dans ces compteurs.

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 1 peuvent être faites. Les seules différences sont que les requêtes sur les instructions (code exécutable) sont également intégrées dans ces compteurs et que l'accès à la mémoire centrale est partagé entre les 4 coeurs de chaque noeud.

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 L3). 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 L1 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 L1 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 3D (Torus send)

Le débit en envoi sur le réseau tore 3D correspond à la somme des données injectées par le noeud de calcul sur le réseau en tore 3D dans les 6 directions existantes (X+, X-, Y+, Y-, Z+ et Z-).

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.

En mode SMP, ces différents trafics peuvent être identifiés. L'affichage est alors enrichi (uniquement dans ce mode d'exécution) avec les champs Torus send pour l'ensemble du trafic, Tor. local send pour les données injectées par les processus et Torus transit pour les données en transit.

Ce réseau sert à l'ensemble des communications point à point et à certaines communications collectives.

Le débit en réception n'est pas disponible (limitations inhérentes aux compteurs hardware des Blue Gene/P).

Débits sur le réseau collectif (Collective send et Collective recv)

Les débits en envoi et en réception sur le réseau collectif sont données. Celles-ci incluent toutes les communications collectives passant par ce réseau (certaines peuvent également passer par le réseau point à point).

Ces compteurs tiennent compte des éventuels en-têtes MPI, mais pas des en-têtes système.

Normalement, la quantité de données envoyée doit correspondre à celle reçue (ne vous fiez pas aux débits, mais bien aux volumes).

Débits d'entrées/sorties (IO write et IO read)

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

Ces compteurs tiennent compte des éventuels en-têtes GPFS, mais pas des en-têtes système.

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 qu'à chaque noeud d'I/O vers l'extérieur (10 Gbps). 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 400 Mo/s par noeud d'I/O. Cela correspond à environ 25% de la crête. Une valeur proche de 25% est donc considérée comme excellente. N'oubliez pas non plus que vous êtes limité par le débit maximum des serveurs de fichiers (8 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 :
    • 0 : résumé pour l'ensemble des noeuds de calcul ;
    • 1 : sorties détaillées pour tous les noeuds de calcul.

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 temps 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 :
    • 0 : résumé pour l'ensemble des noeuds de calcul ;
    • 1 : sorties détaillées pour tous les noeuds de calcul.

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.

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

Remarques

  • Les appels engendrent des surcoûts limités mais non nuls (environ 40 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 sur 2 des 4 coeurs d'un noeud de calcul. libhpcidris instrumente les 2 premiers. Si votre application utilise également les autres coeurs (ce qui est généralement le cas), les mesures seront extrapolées sur ces derniers. Vous obtiendrez alors un message du type : WARNING: values extrapolated on 1024 compute nodes
  • Toute mesure commencée doit être arrêtée une et une seule fois.