PROGRAMMATION HAUT
PERFORMANCE
Pr. J. Abouchabaka Master Big Data et Cloud Computing
Organisation
2
Cours:
Le cours est divisé en trois parties
Travaux pratiques:
Des exercices seront effectués en parallèles avec le
cours.
Mini-projets:
Des mini-projets vous seront distribués une fois on a
terminé la première partie du cours.
Evaluation
3
Examen final: 40%
Travaux pratiques: 30%
Mini-projets: 30%
4
Sommaire
I. Programmation en MPI
II. Programmation en OpenMP
III. Programmation Hybride
Partie I : Programmation en MPI
Plan de la partie
6
1. Introduction
2. Environnements
3. Communications point à point
4. Communications collectives
5. Optimisations
6. Types de données dérivés
7. Topologies
8. Communicateurs
7 1. Introduction
1.1 Historique de MPI
1.2 Définitions
1.3 Concepts de l’échange de messages
1.1 Historique de MPI
8
Novembre 92 (Supercomputing’92 ) : ((formalisation))
d’un groupe de travail créé en avril 92
et décision d’adopter les structures et les méthodes du
groupe HPF (High Performance Fortran).
Brouillon de MPI-1 présenté en novembre 93
(Supercomputing ’93), finalisé en mars 1994.
Cette version vise à la fois la portabilité et la garantie
de bonnes performances.
1.1 Historique de MPI
9
MPI-2 publié en juillet 97, suite à des travaux ayant
commencé au printemps 95.
Standard non élaboré par les organismes officiels de
normalisation (ISO, ANSI, etc.).
D’autres versions sont apparues par la suite:
MPI-1.2 (Juillet 1997) et MPI-1.3 (Mai 2008)
MPI-2.1 (Sep 2008), et MPI-2.2 (Sep 2009)
Sep 2012: le MPI-3.0 standard a été approuvé.
1.2 Définitions
10
Le modèle de programmation séquentiel :
le programme est exécuté par un et un seul processus;
toutes les variables du programme sont allouées dans la
mémoire centrale allouée au processus;
un processus s’exécute sur un processeur physique de la
machine.
1.2 Définitions
11
Le modèle de programmation par échange de
messages :
le programme est écrit dans un langage classique (Fortran,
C ou C++) ;
chaque processus exécute éventuellement des parties
différentes d’un programme ;
toutes les variables du programme sont privées et résident
dans la mémoire locale allouée à chaque processus ;
une donnée est échangée entre deux ou plusieurs processus
via un appel, dans le programme, à des sous-programmes
particuliers.
1.2 Définitions
12
1.2 Définitions
13
Le modèle d’exécution SPMD :
Single Program, Multiple Data ;
le même programme est exécuté par tous les processus;
toutes les machines supportent ce modèle de
programmation et certaines ne supportent que celui-là ;
c’est un cas particulier du modèle plus général MPMD
(Multiple Program, Multiple Data), qu’il peut d’ailleurs
émuler.
1.2 Définitions
14
1.2 Définitions
15
Exemple en C d’émulation MPMD en SPMD
1.3 Concepts d’échange de messages
16
Si un message est envoyé à un processus, celui-ci
doit ensuite le recevoir
1.3 Concepts d’échange de messages
17
Un message est constitué de paquets de données
transitant du processus émetteur au(x) processus
récepteur(s)
En plus des données (variables scalaires, tableaux,
etc.) à transmettre, un message doit contenir les
informations suivantes :
l’identificateur du processus émetteur ;
le type de la donnée ;
sa longueur ;
l’identificateur du processus récepteur.
1.3 Concepts d’échange de messages
18
1.3 Concepts d’échange de messages
19
Les messages échangés sont interprétés et gérés
par un environnement qui peut être comparé :
à la téléphonie ;
à la télécopie ;
au courrier postal ;
à une messagerie électronique ;
etc.
Le message est envoyé à une adresse déterminée
1.3 Concepts d’échange de messages
20
Le processus récepteur doit pouvoir classer et
interpréter les messages qui lui ont été adressés
L’environnement en question est MPI-1 (Message
Passing Interface). Une application MPI est un
ensemble de processus autonomes exécutant chacun
leur propre code et communiquant via des appels à
des sous-programmes de la bibliothèque MPI
1.3 Concepts d’échange de messages
21
Ces sous-programmes peuvent être classés dans les
grandes catégories suivantes :
environnement ;
communications point à point ;
communications collectives ;
types de données dérivés ;
topologies ;
groupes et communicateurs.
22 2. Environnement
2.1 Description
2.2 Exemple
TP 1
2.1 Description
23
Toutes les opérations effectuées par MPI portent sur des
communicateurs. Le communicateur par défaut est
MPI_COMM_WORLD qui comprend tous les processus actifs.
2.1 Description
24
A tout instant, on peut connaître le nombre de processus gérés
par un communicateur donné par la fonction MPI_Comm_size():
De même, la fonction MPI_Comm_rank() permet d’obtenir le
rang d’un processus (i.e. son numéro d’instance compris entre 0
et la valeur renvoyée par MPI_Comm_size() – 1) :
2.2 Exemple
25
TP 1
26
Gestion de l’environnement de MPI : affichage d’un message par
chacun des processus, mais différent selon qu’ils sont de rang pair
ou impair.
Corrigé du TP 1
27
#include "mpi.h"
#include <stdio.h>
int main(int argc, char *argv[]) {
int rang, nb_processus;
MPI_Init( &argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rang);
MPI_Comm_size(MPI_COMM_WORLD, &nb_processus);
if ( (rang % 2) == 0)
printf("Coucou, je suis le processus pair %d\n", rang);
else
printf("Coucou, je suis le processus impair %d\n", rang);
MPI_Finalize();
return 0;
}
28 3. Communications point à point
3.1 Notions générale
3.2 Types de données de base
3.3 Autres possibilités
3.4 Exemple : anneau de communication
3.5 Construction et reconstruction de messages
TP2 : ping-pong
3.1 Notions générale
29
Une communication dite point à point a lieu entre
deux processus, l’un appelé processus émetteur et
l’autre processus récepteur (ou destinataire).
3.1 Notions générale
30
L’émetteur et le récepteur sont identifiés par leur
rang dans le communicateur.
Ce que l’on appelle l’enveloppe d’un message est
constituée :
du rang du processus émetteur ;
du rang du processus récepteur ;
de l’étiquette (tag) du message ;
du nom du communicateur qui définira le contexte de
communication de l’opération.
3.1 Notions générale
31
Les données échangées sont typées (entiers, réels,
etc. ou types dérivés personnels).
Il existe dans chaque cas plusieurs modes de
transfert, faisant appel à des protocoles différents
qui seront vus après.
3.1 Notions générale
32
3.2 Types de données de base
33
3.3 Autres possibilités
34
A la réception d’un message, le rang du processus et l’étiquette
peuvent être des ((jokers)), respectivement MPI_ANY_SOURCE et
MPI_ANY_TAG.
Une communication avec le processus ((fictif)) de rang
MPI_PROC_NULL n’a aucun effet.
Il existe des variantes syntaxiques, MPI_Sendrecv() et
MPI_Sendrecv_replace(), qui enchaînent un envoi et une réception.
On peut créer des structures de données plus complexes à l’aide de
sous-programmes tels que MPI_Type_contiguous(),
MPI_Type_vector(), MPI_Type_indexed() et MPI_Type_struct()
(notion vu après).
3.3 Autres possibilités
35
3.3 Autres possibilités
36
3.3 Autres possibilités
37
Attention ! Il convient de noter que si le sous-programme MPI_Send()
est implémenté de façon bloquante (notion vu après) dans la version
de la bibliothèque MPI mise en oeuvre, le code précédent serait en
situation de verrouillage si à la place de l’ordre MPI_Sendrecv() on
utilisait un ordre MPI_Send() suivi d’un ordre MPI_Recv().
En effet, chacun des deux processus attendrait un ordre de
réception qui ne viendrait jamais, puisque les deux envois resteraient
en suspens. Pour des raisons de portabilité, il faut donc absolument
éviter ces cas-l.
3.4 Exemple : anneau de communication
38
3.4 Exemple : anneau de communication
39
Si tous les processus font un envoi puis une réception,
toutes les communications pourront potentiellement
démarrer simultanément et n’auront donc pas lieu en
anneau (outre le problème déjà mentionné de
portabilité, au cas où l’implémentation du MPI_Send()
est faite de façon bloquante dans la version de la
bibliothèque MPI mise en œuvre) :
3.4 Exemple : anneau de communication
40
Pour que les communications se fassent réellement en anneau, à
l’image d’un passage de jeton entre processus, il faut procéder
différemment et faire en sorte qu’un processus initie la chaîne :
3.4 Exemple : anneau de communication
41
Pour que les communications se fassent réellement en anneau, à
l’image d’un passage de jeton entre processus, il faut procéder
différemment et faire en sorte qu’un processus initie la chaîne :
Pr. J. Abouchabaka
3.4 Exemple : anneau de communication
42
Pour que les communications se fassent réellement en anneau, à
l’image d’un passage de jeton entre processus, il faut procéder
différemment et faire en sorte qu’un processus initie la chaîne :
3.4 Exemple : anneau de communication
43
Pour que les communications se fassent réellement en anneau, à
l’image d’un passage de jeton entre processus, il faut procéder
différemment et faire en sorte qu’un processus initie la chaîne :
3.4 Exemple : anneau de communication
44
Pour que les communications se fassent réellement en anneau, à
l’image d’un passage de jeton entre processus, il faut procéder
différemment et faire en sorte qu’un processus initie la chaîne :
3.4 Exemple : anneau de communication
45
Pour que les communications se fassent réellement en anneau, à
l’image d’un passage de jeton entre processus, il faut procéder
différemment et faire en sorte qu’un processus initie la chaîne :
3.4 Exemple : anneau de communication
46
Pour que les communications se fassent réellement en anneau, à
l’image d’un passage de jeton entre processus, il faut procéder
différemment et faire en sorte qu’un processus initie la chaîne :
3.4 Exemple : anneau de communication
47
Pour que les communications se fassent réellement en anneau, à
l’image d’un passage de jeton entre processus, il faut procéder
différemment et faire en sorte qu’un processus initie la chaîne :
3.4 Exemple : anneau de communication
48
Pr. J. Abouchabaka
3.4 Exemple : anneau de communication
49
3.5 Construction et reconstruction de messages
50
3.5 Construction et reconstruction de messages
51
3.5 Construction et reconstruction de messages
52
Pr. J. Abouchabaka
TP2 : ping-pong
53
Communications point à point : ping-pong entre deux processus
Envoyer un message contenant 1000 réels du processus 0 vers le
processus 1 (il s’agit alors seulement d’un ping)
Faire une version ping-pong où le processus 1 renvoie le message reçu
au processus 0 et mesurer le temps de communication à l’aide de la
fonction MPI_Wtime()
Faire une version où l’on fait varier la taille du message dans une boucle
et mesurer les temps de communication respectifs ainsi que les débits
Les mesures de temps peuvent se faire de la façon suivante :
Corrigé du TP2: version 1
54
/*
* TP2 : Communications point à point : envoi d'un message du
processus 0 au processus 1
*
*/
#include "mpi.h"
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
int rang,iter;
int nb_valeurs=1000;
int etiquette=99;
double valeurs[nb_valeurs];
MPI_Status statut;
Corrigé du TP2: version 1
55
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rang);
if (rang == 0) {
for (iter = 0; iter<nb_valeurs; iter++)
valeurs[iter] = rand() / (RAND_MAX + 1.);
MPI_Send(valeurs,nb_valeurs,MPI_DOUBLE,1,etiquette,MPI_COMM_WORLD);
} else if(rang == 1) {
MPI_Recv(valeurs,nb_valeurs,MPI_DOUBLE,0,etiquette,MPI_COMM_WORLD,&statut)
;
printf("Moi, processus 1, j'ai recu %d valeurs (derniere = %g)"
"du processus 0.\n", nb_valeurs, valeurs[nb_valeurs-1]);
}
MPI_Finalize();
return 0;
}
Corrigé du TP2: version 2
56
/*
* TP2 : Communications point à point : envoi d'un message du
processus 0 au processus 1
*
*/
#include "mpi.h"
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
int rang,iter;
int nb_valeurs=1000;
int etiquette=99;
double valeurs[nb_valeurs];
MPI_Status statut;
Corrigé du TP2: version 2
57
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rang);
if (rang == 0) {
for (iter = 0; iter<nb_valeurs; iter++)
valeurs[iter] = rand() / (RAND_MAX + 1.);
temps_debut=MPI_Wtime();
MPI_Send(valeurs,nb_valeurs,MPI_DOUBLE,1,etiquette,MPI_COMM_WORLD);
MPI_Recv(valeurs,nb_valeurs,MPI_DOUBLE,1,etiquette,MPI_COMM_WORLD,&statut);
temps_fin=MPI_Wtime();
printf("Moi, processus 0, j'ai envoye et recu %d valeurs"
"(derniere = %g) du processus 1 en %f secondes.\n",
nb_valeurs, valeurs[nb_valeurs-1], temps_fin-temps_debut);
} else if(rang == 1) {
MPI_Recv(valeurs,nb_valeurs,MPI_DOUBLE,0,etiquette,MPI_COMM_WORLD,&statut);
MPI_Send(valeurs,nb_valeurs,MPI_DOUBLE,0,etiquette,MPI_COMM_WORLD);
}
MPI_Finalize();
return 0;
}
Corrigé du TP2: version 3
58
/*
* TP2 : Communications point à point : envoi d'un message du
processus 0 au processus 1
*
*/
#include "mpi.h"
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
int rang,iter;
int nb_valeurs=1000;
int etiquette=99;
double valeurs[nb_valeurs];
MPI_Status statut;
Corrigé du TP2: version 3
59
MPI_Init(&argc, &argv);
nb_valeurs[0]=0,nb_valeurs[1]=1,nb_valeurs[2]=10,nb_valeurs[3]=100;
nb_valeurs[4]=1000,nb_valeurs[5]=10000,nb_valeurs[6]=100000;
nb_valeurs[7]=1000000,nb_valeurs[8]=7000000;
MPI_Comm_rank(MPI_COMM_WORLD, &rang);
valeurs = (double *) malloc(nb_valeurs_max*sizeof(double));
for(iter=0; iter<nb_tests; iter++) {
if (rang == 0) {
for (iter2 = 0; iter2<nb_valeurs[iter]; iter2++)
valeurs[iter2] = rand() / (RAND_MAX + 1.);
temps_debut=MPI_Wtime();
MPI_Send(valeurs,nb_valeurs[iter],MPI_DOUBLE,1,etiquette,MPI_COMM_WORLD);
MPI_Recv(valeurs,nb_valeurs[iter],MPI_DOUBLE,1,etiquette,
MPI_COMM_WORLD,&statut);
temps_fin=MPI_Wtime();
Corrigé du TP2: version 3
60
if (nb_valeurs[iter] != 0) {
printf("Moi, processus 0, j'ai envoye et recu %8d valeurs"
"(derniere = %4.2f) du processus 1 en %8.6f secondes, soit "
"avec un debit de %7.2f Mo/s.\n",
nb_valeurs[iter], valeurs[nb_valeurs[iter]-1],
temps_fin-temps_debut,
2.*nb_valeurs[iter]*8/1000000./(temps_fin-temps_debut));
} else
printf("Moi, processus 0, j'ai envoye et recu %8d valeurs en %8.6f "
"secondes, soit avec un debit de %7.2f Mo/s.\n",
nb_valeurs[iter], temps_fin-temps_debut,
2.*nb_valeurs[iter]*8/1000000./(temps_fin-temps_debut));
} else if(rang == 1) {
MPI_Recv(valeurs,nb_valeurs[iter],MPI_DOUBLE,0,etiquette,
MPI_COMM_WORLD,&statut);
MPI_Send(valeurs,nb_valeurs[iter],MPI_DOUBLE,0,etiquette,MPI_COMM_WORLD);
}
}
MPI_Finalize();
return 0;
}
Fin de la Séance