Premier contact

Au cours de ce TP, vous serez amenés à compiler et exécuter des codes d’exemple. Commençons par télécharger et extraire ceux-ci dans votre répertoire personnel :

cd ~
curl -L http://srv-calcul-ambulant/docs/tp-perf/tp-perf.tar.gz | tar -xz
cd tp-perf

N’oubliez pas de compiler avec srun pour ne pas surcharger les coeurs CPU interactifs :

srun make -j$(nproc)

Pour introduire perf stat, nous allons commencer par un code d’exemple extrêmement simple inspiré du benchmark STREAM, appelé scale. Ce code part d’un tableau de nombres flottants, en multiplie chaque élément par une constante, et stocke le résultat dans un autre tableau. Il prend deux paramètres, le premier est la taille des tableaux, et le second est le nombre de fois que le calcul doit être effectué.

Par exemple, pour multiplier deux mille flottants dix millions de fois, nous ferions…

srun --pty ./scale.bin 2048 10000000

Partant de là, pour instrumenter l’exécution de cet exemple avec perf stat, il suffit d’injecter une commande perf stat avant le nom de l’exécutable, comme ceci :

srun --pty perf stat ./scale.bin 2048 10000000

Et nous obtenons ce genre de résultats :

Statistiques par défaut

On voit que par défaut, perf stat nous donne les mêmes informations que la commande time (wall-clock time, user time, system time), mais qu’en complément il nous donne aussi…

  • Des informations sur l’activité système analogues à celles fournies par GNU time :
    • Changements de contexte
    • Migrations d’une tâche entre coeurs CPU logiques
    • Défauts de page
  • Des informations microarchitecturales sur l’activité CPU :
    • Nombre de cycles CPU écoulés
    • Nombre d’instructions exécutées
    • Nombre de branchements (if, boucles…) effectués
    • Nombre de branchements mal prédits

Par ailleurs, sur la colonne de droite, vous remarquerez que perf stat déduit automatiquement pour vous de ces mesures brutes certaines quantités plus utiles pour une analyse de performances :

  • Les nombres d’événements système sont ramenés à une fréquence par seconde
  • Le temps CPU écoulé est traduit en nombre de CPUs utilisés (en divisant par le temps réel)
  • Les cycles sont traduits en fréquence CPU moyenne (en divisant par le temps CPU)
  • Les instructions sont traduits en instructions par cycle
  • Le nombre de branchements mal prédits est ramené à un taux de branchements mal prédits

Il est utile de connaître la fréquence CPU à laquelle on travaille quand on évalue la performance de son code en termes de limites absolues du système à partir d’un temps d’exécution. Par exemple, ici, notre processeur est capable d’effectuer 2 multiplications flottantes vectorisées par cycle avec une largeur de vecteur de 512 bits, donc à une fréquence d’horloge de 3,905 GHz et en simple précision (32 bits), nous pourrions nous attendre à calculer opérations flottantes par seconde. Mais nous n’en calculons que . Par la suite, nous utiliserons perf pour comprendre pourquoi nous calculons ~8.5x plus lentement que prévu.

Le nombre d’instructions exécutées par cycle est une statistique plus difficile à interpréter, dans la mesure où la valeur optimale dépend du calcul qu’on est en train d’effectuer et du modèle de CPU qu’on utilise. Mais on peut garder en tête quelques ordres de grandeur :

  • Même les unités de calcul les plus sollicitées (chargement depuis la mémoire, ALUs vectorielles…) n’existent qu’en deux exemplaires sur la plupart des CPUs. Donc un code qui atteint >= 2 instructions par cycle est potentiellement limité par les unités de calcul du CPU.
  • Les CPU modernes sont conçus pour effectuer du travail en continu, donc un nombre d’instructions par cycle < 1 (qui signifie que le CPU n’exécute aucune nouvelle instruction durant certains cycles) est suspect et mérite un examen plus approfondi.

Exercice : Essayez de relancer le calcul à nombre de calculs constant, mais avec un tableau de plus en plus grand (par exemple, multipliez le premier paramètre par 10 et divisez le second par 10, plusieurs fois de suite). Observez les changements dans la sortie de perf stat alors que la performance de calcul devient limitée par la bande passante du cache L2, puis celle du cache L3, puis celle de la RAM.

Notez que pour vraiment saturer le cache L3 et la bande passante RAM, vous aurez besoin d’utiliser tous les coeurs CPU. Vous pouvez le faire en ajoutant le paramètre --exclusive à srun, mais comme cela empêche les autres participants du TP de lancer des jobs pendant la durée du vôtre, je vous demanderai d’être particulièrement attentif à ce que vos jobs exclusifs soient de courte durée (quelques secondes).

Pendant qu’on parle de multi-threading, essayez d’ajouter l’option --hint=nomultithread à srun pour désactiver l’hyperthreading. Slurm ne vous allouera alors qu’un seul hyperthread au lieu des deux du coeur CPU que vous obtenez quand vous lancer srun sans argument supplémentaire. Comparez les sorties de perf stat dans ces deux configurations, que constatez-vous ?