0% ont trouvé ce document utile (0 vote)
249 vues21 pages

Cours MPI

Transféré par

imene
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
249 vues21 pages

Cours MPI

Transféré par

imene
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

Les librairies de communication parallèles

Trois librairies sont très utilisées pour les systèmes parallèles :

1. MPI : standard pour la programmation dans les architectures parallèles à


mémoire distribuée.
2. OpenMp : standard pour la programmation dans les architectures
parallèles à mémoire partagée.
3. Cuda : Utilisée pour le développement dans les GPU parallèles

1. MPI (Message Passing Interface)

 Les processeurs ne peuvent pas accéder directement à la mémoire d’autres


processeurs
 Les variables sont privées pour chaque processeur
 Les processeurs communiquent par passage de message

M
M

M : message envoyé d’un


1 processeur à un autre
MPI :
 MPI n’est pas un langage de programmation
o Il n’existe pas un compilateur MPI
 MPI est disponible sous forme de fonction/procédure
 Le compilateur C où Fortran invoqué ne connait pas ce que fait
MPI
 MPI est un standard
o Première version en 1993
o Actuellement version 3

Programmation du MPI :

 Le parallélisme est explicite :


 Le programme inclut des instructions spécifiques pour chaque
communication.
 Quelles sont les données à envoyer / recevoir
 Quand envoyer / recevoir
 Synchronisation
 C’est au développeur de déterminer la décomposition parallèle et de
l’implémenter.
 Comment diviser le problème
 Quand communiquer entre les processeurs.

2
EXEMPLE :
 include <mpi.h>

int main (int argc, char* argr [] )

int taille, rang ;

MPI_Init (& argc, & argv) ;

MPI_Com_size (MPI_COM_WORLD, & taille) ;

MPI_Com_rank (MPI_COM_WORLD, & rang) ;

printf (“ le nombre total de processeur est : %d “, taille) ;

printf (“Je suis le processeur numéro %d”, rang);

MPI_Finalize () ;

Explication :

La fonction MPI_Init () : permet d’initialiser l’environnement de MPI

La fonction MPI_Com_size () : donne le nombre total de processeur

La fonction MPI_Com_rank () : donne le numéro du processeur actuel exécutant cette portion


du code.

2. Open MP (Open Multi Processing):

 Utilisée pour les architectures à mémoire partagée:


 Les processeurs accèdent au même espace mémoire.
 Open MP est un ensemble d’extensions pour Fortran, C, C++.
 Le parallélisme est moins explicite que pour MPI
 Le programmeur spécifie quel partie du code à paralléliser et le
compilateur se charge de diviser le programme.
 Une forme très commune de OpenMP est de paralléliser les tâche
d’une boucle.
3
Exemple :

 include <stdio.h>

 include <omp.h>

int main (int argc, char* argr [] )

int taille, rang ;

 pragma omp parallel

rang = omp_get_thread_num();

taille = omp_get_num_threads() ;

printf (“ le nombre total de processeur est : %d \n“, taille) ;

printf (“Je suis le processeur numéro %d”, rang);

return 0 ;

Explication :

 include <omp.h>: permet d’inclure la bibliothèque OpenMP pour pouvoir


utiliser ses différentes fonctions .

La fonction omp_get_num_threads() : donne le nombre total de processeur .

La fonction omp_get_num_threads(): donne le numéro du processeur actuel


exécutant cette portion du code.

4
 pragma omp parallel {….}: permet de spécifier le code à exécuter en
parallèle. Dans l’exemple les instructions :

rang = omp_get_thread_num();

taille = omp_get_num_threads() ;

printf (“ le nombre total de processeur est : %d \n“,


taille) ;

printf (“Je suis le processeur numéro %d”, rang);

Sont exécutées en parallèle.

5
Détails sur la programmation en MPI (Message
passing interface)

1. Définitions :
 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.
 Il existe plusieurs versions de MPI (version 1.0 de 1994  version 3.0 en 2012  version
plus récentes).
 Le programme MPI est écrit dans un langage classique « Fortran, c, c++, java, Python
(MPI4Py),… »
 Un processeur accède aux données d’autres processeurs via une transaction appelée
« message passing » dans laquelle une copie de la donnée (message) est transférée (passed)
depuis un processeur vers un autre.

- Les variables du programme sont


privées.
M1 M1 Mn - Appel dans le programme à des
programmes particuliers

P1 P1 Pn
Transfert de
message (send
receive )
Pg Pg Pg

Le message est constitué des paquets de données (variables , tableaux,…) envoyées du


processeur émetteur vers processeur récepteur.

Le message doit contenir :

 L’identificateur du processeur émetteur (le numéro du processeur émetteur id)


 Le type de la donnée envoyée

6
 La longueur de la donnée envoyée
 L’identificateur du processeur récepteur (le numéro du processeur émetteur id)

Id_Eme Id_rec type long donnée

2. La programmation en MPI :

Il faut inclure un fichier d’entête " mpi.h "

MPI_Init () : permet d’initialiser l’environnement nécessaire

MPI_Finalize() : permet de désactiver l’environnent à la fin

Exemple d’un programme minimal en MPI :

 include " mpi.h "

main (int argc, char * argv[]) {

MPI_Init (& argc, & argv) ;

MPI_Finalize () ;

return 0 ;

On peut écrire un programme fonctionnel juste en utilisant les six routines (fonctions)
suivantes :

MPI_Init : initialiser MPI

MPI_Finalize : terminer MPI

MPI_Comm_size : déterminer le nombre de processeurs

MPI_Comm_rank : déterminer le numéro du processeur courant (le label du processeur


appelant)

MPI_Send : pour envoyer un message

MPI_Recv : pour recevoir un message

7
Les prototypes des fonctions MPI_Init et MPI_Finalize sont :
Cette fonction retourne une valeur
int MPI_Init ( int * argc, char ** argv)
MPI_SUCCESS si l’initialisation est réussite

int MPI_Finalize () ;

Les fonctions basiques de l’envoi et de la réception des messages sont MPI_Send et


Mpi_Recv. Leurs prototypes sont les suivants :

int MPI_Send (void *buf, int count, MPI_Datatype datatype, int dest, int tag MPI_Comm
comm) ;

int MPI_Recv (void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm
comm, MPI_Status * status)
Structure contenant des informations sur
l’opération de réception

3. La notion de communicateur :
Toutes les opérations effectuées par MPI porte sur des communicateurs. Le communicateur
par défaut est MPI_COMM_WORLD qui comprend tous les processeurs actifs.

P1 Le communicateur
P2
P5
P3
P4

 MPI_Comm_size () : permet de connaître le nombre de processeurs géré par une


communicateur.

 MPI_Comm_rank () : permet d’obtenir le rang d’un processeur { 0,…, size-1}

8
Exemple :

 include " mpi.h "

main (int argc, char * argv[]) {

MPI_Init (& argc, & argv) ;

MPI_Comm_rank (MPI_COMM_WORLD, & mon_rang);

MPI_Comm_size (MPI_COMM_WORLD, & nbr_proc);

Printf ( “ je suis le processeur %d parmis %d processeurs“ , mon_rang, nbr_proc);

MPI_Finalize () ;

return 0 ;

Après exécution du programme et si on est dans le processeur 2 par exemple et le nombre


total de processeurs est 10 , on obtient l’affichage suivant :

« Je suis le processeur 2 parmis 10 processeurs »

4. Les différents types de communication en MPI :


Il existe deux types de communication en MPI :

- La communication point à point


- La communication entre groupe de processeurs

4.1 Communication point à point :


Cette communication est entre deux processeurs (émetteur , récepteur). Dans ce type de
communication nous utilisons les deux fonctions : MPI_Send() et MPI_Recv().

L’enveloppe d’un message est constitué par :

1. Rang du processeur émetteur (id)


2. Rang du processeur récepteur (id)
3. Etiquette (tag) du message

9
4. Le communicateur qui défini le groupe de processeurs et le contexte de la
communication

Les donnée échangées sont typées (entiers, réels, ou type personnels). Les différents types en
MPI sont :

MPI_CHAR signed char

MPI_SHORT signed short

MPI_INT  signed int

MPI_LONG  signed long int

MPI_UNSIGNED_CHAR  unsigned char

MPI_UNSIGNED_SHORT unsigned short

MPI_UNSIGNED  unsigned int

MPI_UNSIGNED_LONG  unsigned long int

MPI_FLOAT  float

MPI_DOUBLE double

MPI_LONG_DOUBLE  long double

Il existe 3 types de transfert / communication point à point :


1. En utilisant les fonction d’envoi /réception de messages standards (MPI_Send /
MPI_Recv ):
 Utile pour les envoies de messages de base
 Fonctions d’envoi bloquantes (le retour de l’appel n’est fait que lorsque l’espace
des données est disponible)
2. En utilisant les fonctions non bloquantes
 Initialisent l’envoi et la réception des données et retourne une valeur « status »
immédiatement, on peut ensuite interroger ou attendre la réalisation des opérations
 L’espace mémoire des données n’est pas « accessible » tant que les opérations ne
sont pas finies.
 MPI_Prob() ou MPI_Wait () renvoient les informations sur l’état de l’échange non
bloquant.
3. En utilisant les fonctions d’échange ‘Synchrenous’ et ‘Buffered’

10
Exemple :

 include < studio.h >

 include " mpi.h "

main (int argc, char * argv[]) {

int num_proc, rang, dest, source, Rc, compt, tag=1 ;

char in_msg, out_msg = ‘x’;

MPI_Status etat;

MPI_Init (& argc, & argv) ;

MPI_Comm_rank (MPI_COMM_WORLD, & mon_rang);

MPI_Comm_size (MPI_COMM_WORLD, & nbr_proc);

If (rang ==0) {

Dest =1 ;

Source =1 ;

Rc = MPI_Send (& out_msg, 1, MPI_CHAR, dest, tag, MPI_COMM_WORLD);

Rc= MPI_Recv (& in_msg, 1, MPI_CHAR, source, tag, MPI_COMM_WORLD, & etat);

else if (rang ==1) {

dest = 0;

source =0;

Rc= MPI_Recv (& in_msg, 1, MPI_CHAR, source, tag, MPI_COMM_WORLD, & etat);

Rc = MPI_Send (& out_msg, 1, MPI_CHAR, dest, tag, MPI_COMM_WORLD);

Rc= MPI_Get_cout (& etat, MPI_CHAR, & count);

Printf (“ taache %d : recoit %d char (s) de la taache task %d avec tag %d \n”, rang, count,
etat, MPI_SOURCE, Stat_MPIIAG);

11
MPI_Finalize () ;

return 0 ;

Exemple sur MPI_Send et MPI_Recv:

 include " mpi.h "

main (int argc, char * argv[]) {

int rang, valeur, code, tag=1;

int status[MPI_STATUS_SIZE];

MPI_Init (& argc, & argv) ;

MPI_Comm_rank (MPI_COMM_WORLD, & mon_rang);

MPI_Comm_size (MPI_COMM_WORLD, & nbr_proc);

If (rang == 2) {

valeur =1000 ;

Source =1 ;

MPI_Send (& valeur, 1, MPI_INT, 5, tag, MPI_COMM_WORLD, code);

else if (rang == 5){

MPI_Recv (valeur, 1, MPI_INT, 2, tag, MPI_COMM_WORLD, status, code);

printf ("moi processeur 5 j’ai reçus la valeur : %d du processeur 2", valeur ) ;

MPI_Finalize () ;

Return 0 ; }

12
Remarque : les affichages sont redirigés via le réseau sur le terminal du processeur master,
donc la machine contenant le processeur master affiche les résultats.

- Le processeur master c’est le processeur qui gère la communication, les autres


processeurs sont appelés slave.

4.2 La communication entre groupe de processeurs :


- Au lieu d’une communication entre seulement deux processeurs (point à point) on
peut avoir une communication entre plusieurs processeurs.
- MPI fournit des fonctions (routines) qui envoient des messages à un groupe de
processeurs ou reçoivent des messages d'un groupe de processeurs
 Pas absolument nécessaire pour la programmation
 Plus efficace que les fonctions « point à point » séparées

Exemples : les fonctions MPI suivantes

MPI_Bcast , MPI_Gather, MPI_Scatter, MPI_Reduce, MPI_Allreduce, MPI_Alltoall,


MPI_Barrier ….etc

4.2.1 Les principales fonctions de la communication entre groupe

de processeurs :

- MPI_Bcast : diffusion globale de données (broadcaste)  message envoyé à


tous les autres processeurs en même temps.

- MPI_Scatter (): diffusion sélective de données.


- MPI_Gather() : collecte de données réparties
- MPI_Reduce() : assure la gestion des communication et effectuent des
opérations sur les données transférées (somme, produit, max, min,….).

13
- Les opérations de MPI_Reduce:

MPI_SUM: somme des éléments


MPI_PROD: produit des éléments
MPI_MIN : recherche du minimum
MPI_MAX : recherche du maximum
MPI_MAXLOC : recherche l’indice du maximum
MPI_MINLOC : recherche l’indice du minimum
MPI_LAND : et logique
MPI_LOR : ou logique
MPI_LXOR : ou exclusif logique

A) MPI_Bcast() :

Processeur 0 Processeur 1 Processeur n-1

data data data

…………
…….

MPI_Bcast() MPI_Bcast() MPI_Bcast()

La donnée à envoyer à tous les autres processeurs en Broadcast, cette donnée peut être
une seule valeur ou un tableau (plusieurs valeurs)

Pour pouvoir envoyer une seule donnée du processeur P 0 à tous les autres processeurs nous
avons le choix entre deux approches :

1) Utiliser plusieurs fonctions MPI_Send et MPI_Recv entre P0 et les autres processeurs


2) Utiliser une seule fonction MPI_Bcast() permettant d’envoyer la donnée depuis le
processeur P0 à tous les autres processeurs

14
Le prototype de la fonction MPI_Bcast () est le suivant:

MPI_Bcast(void *buf, int count, MPI_Datatype datatype, int root , MPI_Comm comm) ;

 buf: le tableau à envoyer (on peut envoyer une seule valeur) .


 count : le nombre d’éléments du tableau à envoyer, dans le cas d’une valeur count=1.
 datatype: le type du tableau à envoyer, par exemple: MPI_INT, MPI_FLOAT, etc…..
 root : la racine ou le numéro du processeur qui va envoyer la donnée, dans l’exemple
root=0.
 comm : le communicateur, dans la plupart des cas nous utilisons le communicateur
par défaut MPI_COMM_WORLD qui contient tous les processeurs.

B) MPI_Scatter() :

Processeur 0 Processeur 1 Processeur n-1

recvbuf recvbuf recvbuf

…. …………
Send buffer …….

MPI_Scatter() MPI_Scatter() MPI_Scatter()

Les données à envoyer : on divise le tableau à envoyer en plusieurs éléments, chaque


élément est envoyé à un processeur selon son ordre dans le tableau send buffer.

15
Le prototype de la fonction MPI_Scatter() est le suivant:

MPI_Scatter (void * sendbuf, int sendcount, MPI_Datatype send_datatype, void * recvbuf,


int recvcount, MPI_Datatype recv_datatype , int root , MPI_Comm comm) ;

 sendbuf: le tableau à envoyer.


 sendcount : le nombre d’éléments du tableau à envoyer.
 send_datatype: le type du tableau à envoyer, par exemple: MPI_INT, MPI_FLOAT,
etc…..
 recvbuf: le tableau qui va recevoir la donnée (on peut recevoir une seule valeur).
 recvcount : le nombre d’éléments du tableau qui va recevoir la donnée, dans le cas
d’une seule valeur recvcount=1.
 recv_datatype: le type du tableau à envoyer, par exemple: MPI_INT, MPI_FLOAT,
etc…..
 root : la racine ou le numéro du processeur qui va envoyer les données dans l’exemple
root=0.
 comm : le communicateur, dans la plupart des cas nous utilisons le communicateur
par défaut MPI_COMM_WORLD qui contient tous les processeurs.

16
Exemple MPI_Scatter():
 include " mpi.h "

main (int argc, char * argv[]) {

int rang, nbr_proc,send_count, recv_count, source = 0;

float sendbuf[4][4]=
{{1.0,2.0,3.0,4.0},{5.0,6.0,7.0,8.0},{9.0,10,11.0,12.0},{13.0,14.0,15.0,16.0}};

float recvbuf[4][4]=
La racine  le numéro du processeur qui va
envoyer les données, dans l’exemple 0
MPI_Init (& argc, & argv) ;

MPI_Comm_rank (MPI_COMM_WORLD, & rang);

MPI_Comm_size (MPI_COMM_WORLD, & nbr_proc);

if (nbr_proc == 4) {

MPI_Scatter ( sendbuf, sendcount, MPI_FLOAT, recvbuf, recvcount, MPI_FLOAT, source,


MPI_COMM_WORLD);

printf (“le numéro du processeur courant est :%d, les valeurs reçus pour ce processeur sont :
%f %f %f %f \n”, rang, recvbuf[0], recvbuf[1], recvbuf[2], recvbuf[3]);

else{

printf (“il faut avoir 4 processeurs”); }

MPI_Finalize () ;

return 0 ;

17
C) MPI_Gather() :

Processeur 0 Processeur 1 Processeur n-1

data data data

…. …………
buffer …….

MPI_Gather() MPI_Gather() MPI_Gather()

Le tableau qui va recevoir les données provenant des différents processeurs

MPI_Gather = Rassembler (grouper) les données de n processeurs dans un seul


processeur. Dans le schéma les données sont groupées dans le tableau buffer du
processeur P0.

Le prototype de la fonction MPI_Gather() est le suivant:

MPI_Gather (void * data, int datacount, MPI_Datatype datatype, void * buffer, int
buffercount, MPI_Datatype buffertype , int root , MPI_Comm comm) ;

 data: le tableau à envoyer des différents processeur Pi vers le processeur P0 (on peut
envoyer une seule valeur).
 datacount : le nombre d’éléments du tableau à envoyer, dans le cas d’une seule valeur
datacount=1.
 datatype: le type du tableau à envoyer, par exemple: MPI_INT, MPI_FLOAT, etc…..
 buffer: le tableau qui va recevoir les données collectées de tous les autres
processeurs.
 buffercount : le nombre d’éléments du tableau global qui va recevoir les données,
 buffertype: le type du tableau global (buffer), par exemple: MPI_INT, MPI_FLOAT,
etc…..

18
 root : la racine ou le numéro du processeur qui va recevoir les données collectées,
dans l’exemple root= 0.
 comm : le communicateur, dans la plupart des cas nous utilisons le communicateur
par défaut MPI_COMM_WORLD qui contient tous les processeurs.

Exemple MPI_Gather():
 include " mpi.h "

main (int argc, char * argv[]) {

int rang, valeur, code, tag=1;

int data[10] ; /*data to be ghathered from processors */

MPI_Init (& argc, & argv) ;

MPI_Comm_rank (MPI_COMM_WORLD, & rang);

MPI_Comm_size (MPI_COMM_WORLD, & nbr_proc); Les données vont être envoyées


vers le processeur P0.
If (rang == 0) {

Buffer = (int *) maloc (nbr_proc*10, sizeof(int));

MPI_Gather ( data, 10, MPI_INT, buffer, nbr_proc*10, MPI_INT, 0,


MPI_COMM_WORLD);

MPI_Finalize () ;

return 0 ;

19
D) MPI_Reduce() :
Une grande collection de données va se réduire en une seule valeur en utilisant
les opérations :

MPI_SUM: somme des éléments


MPI_PROD: produit des éléments
MPI_MIN : recherche du minimum
MPI_MAX : recherche du maximum
MPI_MAXLOC : recherche l’indice du maximum
MPI_MINLOC : recherche l’indice du minimum
MPI_LAND : et logique
MPI_LOR : ou logique
MPI_LXOR : ou exclusif logique

Les données contenues dans les processeurs Pi vont être réduites en utilisant une
opération par exemple la somme, puis le résultat obtenu est envoyé au
processeur racine P0.

Il existe plusieurs types de réductions selon l’opération utilisée (addition,


multiplication, …)
Processeur 0 Processeur 1 Processeur n-1

data data data

op
buffer …


MPI_Gather() MPI_Gather() MPI_Gather()


Le tableau qui va recevoir les données provenant des différents processeurs
….

Op : est l’opération de réduction utilisée (par exemple la somme,….)

20
Le prototype de la fonction MPI_Reduce () est le suivant:

MPI_Reduce (void * data, int datacount, MPI_Datatype datatype, void * buffer, int
buffercount, MPI_Datatype buffertype, MPI_Op op , int root , MPI_Comm comm) ;

 data: le tableau à envoyer des différents processeurs Pi vers le processeur P0 (on peut
envoyer une seule valeur).
 datacount : le nombre d’éléments du tableau à envoyer, dans le cas d’une seule valeur
datacount=1.
 datatype: le type du tableau à envoyer, par exemple: MPI_INT, MPI_FLOAT, etc…..
 buffer: le tableau qui va recevoir les données collectées de tous les autres
processeurs.
 buffercount : le nombre d’éléments du tableau global qui va recevoir les données,
 buffertype: le type du tableau global (buffer), par exemple: MPI_INT, MPI_FLOAT,
etc…..
 op : l’opération de réduction utilisée ( MPI_SUM, MPI_PROD, MPI_MIN,
MPI_MAX, MPI_MAXLOC, MPI_MINLOC, MPI_LAND, MPI_LOR, MPI_LXOR)
 root : la racine ou le numéro du processeur qui va recevoir les données collectées,
dans l’exemple root= 0.
 comm : le communicateur, dans la plupart des cas nous utilisons le communicateur
par défaut MPI_COMM_WORLD qui contient tous les processeurs.

21

Vous aimerez peut-être aussi