Programmation parallèle en calcul scientifique
Programmation parallèle en calcul scientifique
Méthodologie M2
Programmation parallèle
pour
le calcul scientifique
Benoît Semelin
2014
Benoit Semelin 2005
Préliminaire
“1000 x ε ~ ε”
Introduction
Généralités
Benoit Semelin 2005
Documents en ligne
Ce cours:
[Link]
OpenMP:
[Link] : spécifications officielles
[Link]
[Link]
[Link] : Cours de l'IDRIS
MPI:
[Link] Cours et memo de l'IDRIS
[Link] Standards mpi
Benoit Semelin 2005
Le calcul parallèle:
qu'est ce que c'est?
Avantages:
Rapidité:
Pour N processeurs, temps de calcul divisé par N, en théorie…
Taille mémoire:
Pour N processeurs, on dispose de N fois plus de mémoire (en général)
Difficultés:
Il faut gérer le partage des tâches.
Calculs multiprocesseurs:
ü Exécution pour une série de conditions initiales différentes.
ü Problème divisible en sous-problèmes indépendants:
Exemple: Mouvement de N particules test dans un champ
extérieur.
Modèles de parallélisme
Architecture matérielle: SISD SIMD MIMD
Single Single Instruction Multiple Instruction
Instruction Multiple Data Multiple Data
Single Data
Architecture Architecture
PC Vectorielle, parallèle
monoprocesseur MMX,SSE, GPU
multiprocesseur
Modèle de programmation:
SPMD MPMD
Le plus utilisé Single Program Multiple Program
Multiple Data Multiple Data
Un seul programme Création dynamique de
(n° processeur = variable) process
Modèle maître-esclave.
Outils de parallélisation:
OpenMP MPI
ordinateur à ordinateur à
mémoire partagée mémoire distribuée
Benoit Semelin 2005
Écriture
Lecture
Lecture
Mémoire Mémoire Mémoire
Mémoire RAM RAM RAM RAM
èTrès peu de surcoût de parallélisation. èIl faut ajouter un réseau de com performant.
èArchitecture coûteuse.
Benoit Semelin 2005
Revue d'effectif
dans le monde
Tous les ans, une liste des 500 plus gros ordinateur est publiée sur: [Link]
10 CS-storm Cray, Intel Xeon E5-2660v2 IBM United States 72800 3.57
bullx DLC, Xeon E5-2690v3
26 Occigen Bull SA France 50544 2.1
12C 2.6GHz, Infiniband FDR
Benoit Semelin 2005
Revue d'effectif
pour la recherche en France
- l’ IDRIS: IBM Bluegene (Turing) 1.25 Pflops + IBM (Ada) 230 Tflops
Revue d'effectif 3:
à l’obsevatoire
Dans le cadre de cette option, nous travaillerons sur:
MesoPSL:
Momentum:
Outils de parallélisation
La parallélisation peut-être effectuée à divers niveaux:
Langages, ou extensions de langages:
è CUDA
è OpenCL
Bibliothèques:
è Message Passing Interface (MPI)
è Pthreads (langage C)
Directives de compilation:
è OpenMP
è Directives d’accélération pour GPU ou autre.
Compilateurs: efficacité très faible.
è Intel Fortran/C compiler: gratuit.
Benoit Semelin 2005
OpenMP
Benoit Semelin 2005
OpenMP:
modèle de programmation
Benoit Semelin 2005
La parallélisation facile:
OpenMP
OpenMP est un ensemble de directives de compilation pour
paralléliser un code sur une architecture SMP (interfaces Fortran, C
et C++)
Le compilateur interprète les directives OpenMP (si il en est capable!)
Les standards d'OpenMP datent de 1997, ceux d'OpenMP-2 de 2000, OpenMP-3
2008. Les développeurs de compilateurs les implémentent.
Modèles d'exécution OpenMP: Thread ≃ process (≠
Procces processeur)
princip
al
!$OMP_DO_SCHEDULE(DYNAMIC,500)
OpenMP:
régions parallèles,
comportements des variables
Benoit Semelin 2005
Comportements possibles
d'une variable
Variable SHARED Variable PRIVATE
X=1. X=1.
X=2. X=2.
X=? X indéterminé.
contrôlable par
LASTPRIVATE
Benoit Semelin 2005
Comportement SHARED/PRIVATE
d'une variable
SUBROUTINE compute_grad(field,grad,n,cellsize,TYPEMIN)
SHARED et PRIVATE sont deux
USE VARIABLES
clauses qui spécifient le
INTEGER, INTENT(IN) :: n
comportement des variables dans REAL(KIND=8), INTENT(IN) :: cellsize
REAL(KIND=8), DIMENSION(n), INTENT(IN) :: field
une zone parallèle: REAL(KIND=8), DIMENSION(n), INTENT(OUT) :: grad
INTEGER, INTENT(IN) :: typemin
Ø SHARED: la variable a la même REAL(KIND=8) :: val,g1,g2,fx
INTEGER :: i,ip,im
valeur sur tout les processeurs. C'est le
statut par défaut. Attention à la !$OMP PARALLEL PRIVATE(ip,im,g1,g2,fx)
synchro si on la modifie sur un ! Autre directive OMP
Autres clauses de
comportements de variables
On peut changer le comportement défaut:
!$OMP PARALLEL DEFAULT(PRIVATE | SHARED | NONE)
Clauses supplémentaires:
Ø FIRSPRIVATE(X): X est PRIVATE et initialisée à sa valeur juste avant la zone
parallèle.
Ø REDUCTION(*,X): X est PRIVATE et un produit des valeurs de X sur les
différents threads est effectué en fin de zone parallèle et stocké dans X. L'opérateur
peut-être + , - , * , .OR. , .AND. , MAX, MIN, etc... Remplace les statuts PRIVATE
ou SHARED.
Ø LASTPRIVATE(X): X est PRIVATE. La valeur de X du thread exécutant la
dernière mise à jour de X est conservée en fin de zone parallèle.
Ø COPYPRIVATE(X): Pour diffuser une variable privée (Directive SINGLE
uniquement).
Benoit Semelin 2005
zone parallèle.
Ø Certaines procédures/fonctions utilisent et modifient des variables globales.
Les valeurs de /COORD/ sont copiées dans les répliques privées de chaque thread en
entrée dans les régions parallèles.
Benoit Semelin 2005
OpenMP:
partage du travail
entre threads
Benoit Semelin 2005
Partage du travail:
distribuer une boucle
SUBROUTINE compute_grad(field,grad,n,cellsize,TYPEMIN)
La directive DO se place juste avant le
USE VARIABLES
début d'une boucle, elle répartit les
INTEGER, INTENT(IN) :: n
itérations entre les processeurs. REAL(KIND=8), INTENT(IN) :: cellsize
REAL(KIND=8), DIMENSION(n), INTENT(IN) :: field
REAL(KIND=8), DIMENSION(n), INTENT(OUT) :: grad
è Pas de DO WHILE ! INTEGER, INTENT(IN) :: typemin
REAL(KIND=8) :: val,g1,g2,fx
è Le mode de répartition dépend de la INTEGER :: i,ip,im
Stratégies de répartition
des itérations
La clause SCHEDULE permet de contrôler la stratégie de répartition des itérations
d'une boucle entre les processeurs, elle admet 2 arguments: SCHEDULE(stratégie,N).
Stratégies possibles:
ü STATIC: Chaque thread reçoit à tour de rôle N itérations à traiter. La distribution
s'effectue dans un ordre fixé, éventuellement en plusieurs tours. N est optionnel, si il n'est
pas spécifié N~ nb itération / nb threads.
ü DYNAMIC: Chaque thread reçoit N itérations à traiter. Dès qu'un thread a fini, il en
reçoit N autres, jusqu'à épuisement du travail. N est optionnel, par défaut N=1.
ü GUIDED: Les itérations sont divisées en paquets de taille exponentiellement
décroissante. Les paquets sont distribués dynamiquement. La taille du plus petit paquet
est N.
ü RUNTIME: Le choix de la stratégie est reporté au moment de l'exécution. Il sera
alors déterminé par le contenu de la variable d'environnement OMP_SCHEDULE.
Un exemple:
calcul de potentiel périodique
PROGRAM POTPER
IMPLICIT NONE
Programme pour calculer une REAL(KIND=8),PARAMETER :: BOXSIZE=1.
INTEGER, PARAMETER :: NREP=200
interaction gravitationnelle avec INTEGER, PARAMETER :: NDEF=40
INTEGER :: i,j,k,l
des conditions de bord REAL(KIND=8) :: x1,y1,z1,x2,y2,z2,pot,dist
périodiques. y1=0.
z1=0.
do i=1,ndef
x1=dble(i)/ndef*BOXSIZE
pot=0.
Remarques: !$OMP PARALLEL PRIVATE(k,l,x2,y2,z2,dist) REDUCTION(+:pot)
!$OMP DO SCHEDULE(DYNAMIC)
Il existe un algorithme plus do j=-nrep,nrep
x2=dble(j)*BOXSIZE
efficace (décomposition de la do k=-nrep,nrep
somme en deux parties). y2=dble(k)*BOXSIZE
do l=-nrep,nrep
On pourrait paralléliser la z2=dble(l)*BOXSIZE
dist=sqrt((x2-x1)**2+(y2-y1)**2+(z2-z1)**2)
première boucle! if(dist < BOXSIZE*NREP*0.95) then
Remarquer l'utilisation de la pot=pot+(x2-x1)/(sqrt((x2-x1)**2+(y2-y1)**2+(z2-z1)**2))**3
endif
clause REDUCTION. enddo
Zones parallèles longues => bon enddo
enddo
speedup !$OMP END DO
!$OMP END PARALLEL
print*,x1,pot
enddo
END PROGRAM POTPER
Benoit Semelin 2005
Partage du travail
en l'absence de boucle
La directive SECTIONS amorce une
zone où le code est découpé en ...
morceaux. END SECTIONS clôt cette
!$OMP PARALLEL
zone !$OMP SECTIONS
Ces morceaux sont séparés par des
directives SECTION. !$OMP SECTION
CALL UPDATE_XPOS()
Chaque morceau (section) sera exécuté
une fois unique par un des threads. !$OMP SECTION
CALL UPDATE_YPOS()
L'ordre d'exécution des sections doit
!$OMP SECTION
être indifférent! CALL UPDATE_ZPOS()
Exécution exclusive:
directives SINGLE et MASTER
Si, dans une zone parallèle, on souhaite qu'une partie du code soit
exécutée par un seul thread, on a le choix entre 2 directives:
OpenMP:
Problèmes de
synchronisation.
Synchronisation simple:
BARRIER
La directive BARRIER synchronise les threads: tous s'arrêtent au
niveau de la directive jusqu'à ce que le dernier soit arrivé. Puis ils
continuent tous. Syntaxe:
!$OMP BARRIER
N'admet aucune clause.
Une BARRIER est implicitement incluse dans les directives
suivantes:
Ø END PARALLEL
Ø END DO (sauf clause NOWAIT)
Ø END SECTIONS (sauf clause NOWAIT)
Ø END SINGLE (sauf clause NOWAIT)
Ø END WORKSHARE (sauf clause NOWAIT)
Pas de BARRIER implicite de END MASTER !
Benoit Semelin 2005
On a rarement besoin d'utiliser FLUSH directement. Il est plus simple (et plus lent)
d'utiliser BARRIER.
Benoit Semelin 2005
Dans l'ordre:
directive ORDERED
La directive ORDERED permet, à !$OMP PARALLEL PRIVATE(XLOCAL)
OpenMP:
variables d'environnement
et bibliothèque standard
Benoit Semelin 2005
Variables d'environnement
Bibliothèque standard:
contrôle de l'environnement
On peut souvent se passer complètement de ces fonctions.
Modèles d'exécution: (appel depuis une zone séquentielle)
è OMP_SET_DYNAMIC(boolean): Subroutine. Active/désactive l'ajustement
dynamique du nombre de thread: fixe OMP_DYNAMIC.
è OMP_GET_DYNAMIC(): Function. Retourne la valeur actuelle de OMP_DYNAMIC.
è OMP_SET_NESTED(boolean): Subroutine. Fixe OMP_NESTED
è OMP_GET_NESTED(): Function. Retourne la valeur actuelle de OMP_NESTED .
Contrôle du nb de thread/processeur:
è OMP_SET_NUM_THREADS(entier): Subroutine. Fixe le nombre de threads
(maximum) pour les prochaines zones parallèles (valeur de OMP_NUM_THREADS).
Appel depuis une zone séquentielle.
è OMP_GET_NUM_THREADS(): Function. Retourne le nb réel de threads utilisées à
l'instant t.
è OMP_GET_MAX_THREADS(): Function. Retourne la valeur de
OMP_NUM_THREADS (nb maximal de threads).
è OMP_GET_NUM_PROCS(): Function. Retourne le nombre de processeurs utilisés.
Benoit Semelin 2005
Bibliothèque standard:
contrôle manuel de la parallélisation
Utilisation de verrous:
Un verrou est libre ou possédé par un thread. Une série de fonction OpenMP
permet de les manipuler, par exemple d'attendre à un point du code que le
verrou soit libéré par un autre thread.
décomposition en domaines
Benoit Semelin 2005
Décomposition en
domaine / fonction
Schéma basique d'un ordinateur: x, y
CPU
z Mémoire
- Une unité de calcul opère sur des données z=x+y
qu’elle lit et écrit en mémoire.
Décomposition en domaine:
stratégie.
Les contraintes:
- Les communications (échange de données entre domaines) ont un coût en temps
CPU: il faut minimiser la quantité de communication (nombre et taille).
- Il faut que chaque domaine représente la même quantité de travail. Il faut minimiser
les fluctuations de charge et assurer l'équilibrage dynamique des charges.
Décomposabilité
Exemple de décomposition 1:
résolution d'EDP sur grille.
Résolution de l'équation de Burger: ∂v ∂v 1 ∂ 2v
+v =
∂t ∂x Re ∂x 2
On résout l'équation sur une grille 1-D, par une méthode de différences finies, et avec
un schéma explicite (!) à pas de temps constant.
Le domaine 2 se
restreint spatialement
mais conserve autant
de cellules que 1 et 3.
Benoit Semelin 2005
Exemple de décomposition 2:
L'algorithme Treecode.
Dynamique d'un système N-
corps:
Algorithme Treecode:
exemple de décomposition.
communications.
Benoit Semelin 2005
Temps de transmission
d'un message.
Sur une architecture à mémoire distribuée, les unités de calculs doivent
échanger des messages. Ces communications ne sont pas instantanées.
Conséquences:
Modes de communications
entre process.
Exemple de mécanisme de communication: (two-sided, buffered,
synchrone)
Lecture Ecriture
CPU 1 CPU 1
RAM
RAM
Ecriture ''Prêt à envoyer'' Lecture
Buffer de Buffer ''Prêt à recevoir'' Buffer de Buffer
réception d'envoi réception d'envoi
Transmission de
données
Mode de commnications: définitions
One-sided / Two-sided:
Un seul processeur gère tout les éléments de la com / les deux processeurs intervienent, ils
gèrent chacun une partie des éléments de la com.
Synchrone / Asynchrone:
Le processeur qui envoit (1) attend / n'attend pas que le proccesseur qui recoit (2) ait
signalé qu'il était prêt. En mode asynchrone, le processeur (1) transmet les données qui
sont écrites dans le buffer de réception du processeur (2). Il viendra les lire plus tard,
quand le code lui dira de faire.
Benoît Semelin 2005
Communication globales:
les contraintes matérielles
Dans un ordinateur multi-processeur, chaque processeur n'a pas un lien avec tout les
autres pour les communications (trop cher). Il faut pourtant tenter de l'optimiser.
Communications globales
sur l'hypercube
Une implémentation naïve de la réduction d'une variable (somme des valeurs
sur les N processeurs) nécessite N communications successives:
X1+X2+X3 X1+X2 X1
CPU8 CPU7 CPU6 CPU5 CPU4 CPU3 CPU2 CPU1
X8 X7 X6 X5 X4 X3 X2 X1
Une implémentation de la
CPU7 CPU8
réduction en utilisant le principe
1 X7 1 X8
de l'hypercube nécessite log(N)
communications successives. CPU4 CPU3
Le principe de l'hypercube peut servir pour X4 X3
organiser des communications globales
type REDUCTION dans un code. MPI s'en 2 CPU5 CPU6
2
sert pour ses fonctions de communications 1 X5 1 X6
globales génériques. Si ces routines
génériques ne répondent pas aux besoins de
l 'utilisation, il peut utiliser le principe de CPU1 3 CPU2
l'hypercube lui même. X1 X2
Benoit Semelin 2005
Performances d'un
code parallèle.
Coût des communications:
Si le temps de communications = 10% du temps de calcul, inutile d'utiliser
plus de 5-10 processeurs. Il faut minimiser le temps de communication.
Pistes pour réduire les communications:
- Recouvrement des bords de domaines (Simulation sur grille)
- Duplication des données critiques (Treecode)
Equilibrage de charge:
Si un processeur est 1,3 fois plus lent qu'un autre, on obtient le même temps
de calcul qu'avec 0.7 fois moins de processeurs.
Equilibrage dynamique des charges (''overload diffusion''):
- Applicable, par ex, quand on calcule une évolution temporelle.
- Chaque domaine a une taille Si (t) variable . A chaque pas de temps, on
calcule Ti(t), le temps de calcul sur chaque processeur.
- On adapte Si, par exemple: Si (t+dt)=Si (t)*Tmoy(t)/ Ti(t) (stabilité?)
Benoit Semelin 2005
MPI
Benoit Semelin 2005
Introduction
MPI est une bibliothèque de communication pour le parallélisme sur
architectures à mémoire partagée (Fortran, C, C++).
Les standards MPI:
MPI 1 (1994): Le coeur de la bibliothèque
- Communicateurs
- Communications locales et globale
- Définition de type de variable
- Topologies
MPI 2 (1997): extensions et support C++ et f90
- Communications ''one-sided''
- Entrées-sorties parallèles (non traitées ici)
Implémentations gratuites:
LAM: [Link] (remplacé par OpenMPI)
MPI 1 et 2 (en partie) sur grille de PC ou architectures SMP.
MPICH2: [Link]
MPI 1 et 2 (en partie) sur grille de PC ou architectures SMP.
Benoit Semelin 2005
MPI:
fonctions générales,
environnement
Benoit Semelin 2005
Environnement système
program EX_0
Il faut inclure la bibliothèque
implicit none
MPI. include ''mpif.h'' !(ou USE MPI avant implicit none)
integer :: nb_procs,rang,err
- mpif.h en fortran. call MPI_INIT (err)
- mpi.h en C/C++. call MPI_COMM_SIZE ( MPI_COMM_WORLD ,nb_procs,err)
call MPI_COMM_RANK ( MPI_COMM_WORLD ,rang,err)
print *, 'Je suis le processus' ,rang, 'parmi' ,nb_procs
Exemples de commandes call MPI_FINALIZE (err)
de compilation: end program EX_0
Sur tucana:
- mpif90 EX_0.f90 (ou mpif77, mpicc, mpiCC)
- mpd & (la première fois)
- mpiexec -n 8 EX_0.out
- mpdallexit
Benoit Semelin 2005
Initialisation et sortie
program EX_0
Avec MPI, on définit une seule
zone parallèle dans le implicit none
include ''mpif.h'' !(ou USE MPI avant implicit none)
programme, souvant l'ensemble integer :: nb_procs,rang,err
du code.
call MPI_INIT (err)
call MPI_COMM_SIZE ( MPI_COMM_WORLD ,nb_procs,err)
Les parties du code avant et après call MPI_COMM_RANK ( MPI_COMM_WORLD ,rang,err)
print *, 'Je suis le processus' ,rang, 'parmi' ,nb_procs
la zone parallèle sont locale sur call MPI_FINALIZE (err)
chaque processeur. Pas de
end program EX_0
communications possibles.
Début de zone parallèle Fin de zone parallèle
En fortran: call MPI_INIT(err) En fortran: call MPI_FINALIZE(err)
En C: MPI_Init(); En C: MPI_Finalize();
En C++: MPI::Init(); En C++: MPI::Finalize();
Fonction collective! (appelée par tous les process) Fonction collective! (appelée par tous les process)
Pour une définition précise des fonctions (type, arg optionnels, etc...) voir aide
mémoire IDRIS et manuel de référence MPI en ligne.
Benoit Semelin 2005
Goupes et communicateurs:
objets de base de MPI
Groupes et communicateurs sont des objets MPI opaques: on les
manipule par une ''poignée'' (variable de type INTEGER en fortran)
- Groupe: Ensemble de process.
0 1
2 3
2 3
1
0 2
1 0 1
3
2 4
3 2 3
Benoit Semelin 200
Manipulation des
groupes et communicateur.
L'appel à MPI_INIT() définit un communicateur par défaut: MPI_COMM_WORLD (constante
entière MPI)
Souvant, MPI_COMM_WORLD est le seul usage des communicateurs/groupes que fait un code.
Mais il est parfois intéressant de créer des sous-groupes/sous-communicateur.
Quelques fonctions pour créer-manipuler-détruire les groupes et communicateur.
MPI_COMM_SIZE(comm,size,err): locale. Renvoie size, le nb de process dans comm.
MPI_COMM_RANK(comm,rank,err): locale. Renvoie rank, le n° du process appelant.
MPI:
MPI_SEND et MPI_RECV:
communication de base.
program EX_1 ! (prog de 2 process)
Nombre
implicit none Début
de variables
du tableau d'envoi Type MPI
include ''mpif.h'' à envoyer
des variables
(adresse)
integer :: rang,err,tag1,tag2 à envoyer
integer, dimension( MPI_STATUS_SIZE ) :: statut
Numéro
real, DIMENSION(10) :: x,x_remote du process
destinataire.
call MPI_INIT (err)
tag1=1
tag2=2
Etiquette
call MPI_COMM_RANK (MPI_COMM_WORLD ,rang,err) du message
x = rand()
if(rang = = 0) then
call MPI_SEND(x,10,MPI_REAL,1,tag1,MPI_COMM_WORLD,err)
Communicateur
call MPI_RECV(x_remote,10,MPI_REAL,1,tag2,MPI_COMM_WORLD,statut,err)
endif
if(rang = = 1) then
call MPI_RECV(x_remote,10,MPI_REAL,0,tag1,MPI_COMM_WORLD,statut,err)
call MPI_SEND(x,10,MPI_REAL,0,tag2,MPI_COMM_WOLD,err)
endif
Statut de
print*,'rang: ',rang, ' liste: ',x,x_remote l'opération de
call MPI_FINALIZE (err) Numéro réception.
du process
end program EX_0 source
Benoit Semelin 200
MPI_SEND et MPI_RECV:
détails d'utilisation.
- Pour des raisons de portabilité (grille hétérogène), il faut donner un ''type MPI''
pour les variables envoyées. Voici la correspondance pour Fortran:
- On peut aussi envoyer des types MPI_PACKED et des types définis par l'utilisateur
(structures).
Modes de communication:
définitions
Communication bloquante: l'appel à la fonction ne ''retourne'' que quand les ressources utilisées (p. e.
emplacement mémoire de la variable envoyée) peuvent être réutilisées et que la fonction ''complète''.
Communication non-bloquante: l'appel à la fonction retourne avant que les ressources aient été
libérées, et le programme continue. Il faut s'assurer qu'on ne modifie pas les ressources avant que la
communication soit effectivement complétée. La fonction ne complète qu'à ce moment là.
Mode d'envoi ''buffered'': le message est stoké dans une mémoire système locale avant d'être envoyé.
La fonction d'envoi (bloquante ou non) complète quand la copie est finie mais avant l'envoi.
Mode d'envoi ''synchrone'': l'envoi effectif du message ne commence que quand le process reçoit le
message « prêt » d'une commande de réception correspondante. La fonction d'envoi ne complète qu'à ce
moment là.
Mode d'envoi ''ready'': le process émetteur suppose que le recepteur est près à recevoir sans vérifier, et
envoie le message (meilleures performances). Si le récepteur n'a pas exécuté la commande de réception
correcpondante -> Erreur! La fonction complète quand l'evoi est fini.
Mode d'envoi ''standard'': Suivant la disponibilité en mémoire système, MPI choisit lui même entre
les modes ''buffered'' et ''synchrone''.
Autres fonctions de
communication point à point
Il existe une panoplie de fonctions pour effectuer des communications
bloquante ou non dans le différent mode. Elle s'utilisent avec les même
arguements que MPI_SEND et MPI_RECV, plus un argument ''request''
pour les fonctions non-bloquantes.
Bloquant Non-bloquant
Standard MPI_SEND MPI_ISEND
Synchrone MPI_SSEND MPI_ISSEND
Ready MPI_RSEND MPI_IRSEND
Buffered MPI_BSEND MPI_IBSEND (nécessite MPI_BUFFER_ATTACH)
- Le mode buffered nécessite une copie mémoire de plus mais permet de continuer les calculs à
coup sûr.
- Le mode ready diminue la latence, mais est délicat à utiliser (synchronisation).
Benoit Semelin 200
Gérer la complétion
d'une opération non-bloquante.
Syntaxe d'un envoi non-bloquant:
MPI_ISEND(val,count,datatype,dest,etiquette,comm,requete,err)
Le code peut continuer mais garde la trace de l'envoi grâce à requete (poignée vers
un objet MPI). Il peut utiliser requete ultérieurement pour contrôler si la
communication est complète:
MPI_WAIT(requete,statut,err): attentant que la communication associée à requete soit
complétée. statut contient des infos sur la communication.
MPI_TEST(requete,flag,statut,err): flag=true si la communication associée à requete est
complétée. Sinon, flag=false, et requete est désallouée! Pas de vérification ultérieure possible.
MPI_REQUEST_GET_STATUS(request,flag,status,err): teste la complétion sans
désallouer la requête, même si flag=true.
MPI_REQUEST_FREE(request,err): désalloue la requête.
Communications
non-prédictibles
Dans certains algorithmes, le besoin de communications peut dépendre
des données initiales (ex: modif dynamique des domaines):
- Les besoins de l'envoi sont déterminés localement par le calcul.
- Comment déterminer les réceptions à effectuer?
On peut tester périodiquement l'arrivée de messages sans les recevoir.
MPI_IPROBE(source,etiquette,comm,flag,status): non bloquant!
flag détermine si un message est arrivé ou non. source et etiquette
peuvent prendre les valeurs MPI_ANY_SOURCE et MPI_ANY_TAG,
si flag=true, status contient la source et l'étiquette.
Synchronisation
Synchronisation globale:
Synchronisation locale:
On peut utiliser une communication synchrone bloquante
(MPI_SSEND/RECV) pour synchroniser 2 process.
Si on a besoin de synchroniser de façon répétée un sous-groupe de
process, il faut sans-doute définir un nouveau sous-groupe-MPI et un
nouvel intra-communicateur.
Benoit Semelin 200
MPI:
communications globales
(fonctions collectives)
Benoit Semelin 200
Communication de
type ''broadcast''
Il s'agit de diffuser aux autres
processeurs une valeur connue sur
un seul:
MPI_BCAST(address,count,datatype,root,comm,err) CPU 1
X0
root désigne le rang du process
qui diffuse l'information. Les
autres arguments ont la même
signification que dans
MPI_SEND. CPU 0 CPU 2
X0 X0
C'est une fonction collective :
elle doit être appelée par tous les
process.
Communication de
type ''gather''
Il s'agit de rassembler sur l'un des process
des données réparties sur l'emsemble des
process du communicateur :
MPI_GATHER(s_add , s_count , s_type , r_add , r_count , r_type , root , comm , err)
Autres communications de
type ''gather''
Pour recevoir une quantité de données différente de chaque process ou les disposer de
manière non-consécutive dans le tableau de réception, on utilise la version vecteur:
MPI_GATHERV(s_add , s_count , s_type , r_add , r_counts , disp , r_type , root , comm , err)
Pour réaliser une opération sur les données reçues, on utilise une fonction de réduction:
- Les valeurs dans s_add sur les différents process sont combinées, élément à élément si count≠1,
et stockées dans r_add sur le process root. La combinaison est faite par l'opérateur op.
- En fortran op peut être: MPI_SUM, MPI_PROD, MPI_MAX, MPI_MIN, etc...
- Il est possible de créer ses propres opérateur grâce à MPI_OP_CREATE.
Benoit Semelin 200
Communications de
type ''scatter''
Il s'agit de répartir sur les process du
communicateur des données présentes sur
l'un des process:
MPI_SCATTER(s_add , s_count , s_type , r_add , r_count , r_type , root , comm , err)
- root désigne le rang du process qui
envoie les données. s_add désigne CPU 1
l'adresse (nom de variable en fortran) où
les données à envoyer sont stockées. X1
- Les données sont envoyées par paquet de
s_count aux process du communicateur
par ordre de rang.
X0
- En général r_count = s_count. CPU 0 CPU 2
X1
- Il existe une variante ''vecteur'':
X2 X2
MPI_SCATTERV
X3
- Il existe aussi:
MPI_REDUCE_SCATTER
Opére une reduction sur des tableaux, CPU 3
élément à élément, et stocke le résultat
pour les i-ème éléments sur le process de X3
rang i.
Benoit Semelin 200
Communications de
type ''allgather''
Même fonction que MPI_GATHER,
mais chaque process reçoit le résultat:
MPI_ALLGATHER(s_add , s_count , s_type , r_add , r_count , r_type , comm , err)
- Pas de root !
CPU 1
- Autres arguments identiques à
X0 X1 X2X3
MPI_GATHER.
- Equivalent à NB_PROCS appels à
MPI_GATHER avec à chaque fois un
process différent comme root. X0 X0
CPU 2
CPU 0
- Il existe une version vecteur: X1 X1
MPI_ALLGATHERV X2 X2
- Et une version avec réduction: X3 X3
MPI_ALLREDUCE
X0 X1 X2 X3
CPU 3
Benoit Semelin 200
Communications de
type ''all-to-all''
Même fonction que
MPI_ALLGATHER, mais chaque
process reçoit des données
différentes:
CPU 1
MPI_ALLTOALL(s_add , s_count , s_type ,
r_add , r_count , r_type , comm , err) W1 X1 Y1 Z1
X0 X1 X2 X3
- A rg u m e n t s i d e n t i q u e s à
MPI_ALLGATHER.
CPU 2
X0 W1 Y1 X2
- Il existe deux versions vecteurs, Y0 W
2
Y2 Y2
pour pouvoir faire varier le
Z0 W3 Y3 Z
nombre, le type et la position des 2
données envoyées et reçues:
MPI_ALLTOALLV Z0 Z1 Z2 Z3
MPI_ALLTOALLW
W3 X3 Y3 Z3
Avec MPI_ALLTOALLW on peut
tout faire! Ou presque... CPU 3
Benoit Semelin 200
Types dérivés:
variables MPI
définies par l'utilisateur
Benoit Semelin 200
INTEGER :: err,MPI_vector
call MPI_TYPE_CONTIGUOUS(3,MPI_REAL,MPI_vector,err)
call MPI_TYPE_COMMIT(MPI_vector,err)
... communications ...
call MPI_TYPE_FREE(MPI_vector,err)
On définit ici un type MPI_vector, constitué de 3 MPI_REAL stockés consécutivement
en mémoire. MPI_vector peut servir à définir d'autres types dérivés.
Il faut ''compiler'' le type avec MPI_TYPE_COMMIT avant de pouvoir l'utiliser dans
une communication.
Benoit Semelin 200
Les éléments foncés constituent la section x(2:12:3) du tableau x(1:12). Définissons un type MPI
correspondant.
INTEGER :: err,MPI_vector_section
REAL, DIMENSION(12) :: x,y
call MPI_TYPE_VECTOR(4,1,3,MPI_REAL,MPI_vector_section,err)
call MPI_TYPE_COMMIT(MPI_vector_section,err)
call MPI_ALLREDUCE(x(2),y(2),1,MPI_vector_section,MPI_SUM,MPI_COMM_WORLD,err)
On peut définir des sections avec pas variables entre blocs avec MPI_TYPE_CREATE_INDEXED_BLOCK,
et avec pas et longueusr de blocs variables avec MPI_TYPE_INDEXED.
Benoit Semelin 200
call MPI_TYPE_EXTENT(MPI_INTEGER,MPI_integer_length,err)
call MPI_TYPE_EXTENT(MPI_REAL,MPI_real_length,err)
call MPI_TYPE_EXTENT(MPI_LOGICAL,MPI_logical_length,err)
} Pour la portabilité
(taille mémoire en bits)
}
call MPI_TYPE_CONTIGUOUS(3,MPI_REAL,MPI_vector,err)
call MPI_TYPE_COMMIT(MPI_vector,err) Définition d'un type vecteur
call MPI_TYPE_EXTENT(MPI_vector,MPI_vector_length,err)
}
array_of_block_length(1:3) = (/1,2,1/)
array_of_types(1:3) = (/MPI_INTEGER,MPI_vector,MPI_REAL/)
array_of_displacement(1) = 0 Description du
array_of_displacement(2) = MPI_integer_length type structure
array_of_displacement(3) = array_of_displacement(2) + 2*MPI_vector_length (déplacements en bits)
MPI:
communications ''one-sided''
Benoît Semelin 200
Introduction
Que faire quand le process receveur ne sait pas qu'il doit recevoir?
MPI_PUT / MPI_GET:
écriture / lecture
Il y a 2 fonctions principales pour réaliser une communication one-sided:
MPI_PUT(loc_add , loc_count , loc_type , remote_rank , remote_disp &
& , remote_count , remote_type , win , err)
MPI_GET(loc_add , loc_count , loc_type , remote_rank , remote_disp &
& , remote_count , remote_type , win , err)
Ø loc_add: adresse de début des données sur le process qui appelle la fonction
Ø remote_disp: Définit l'endroit où lire/écrire les données par le déplacement en
disp_unit par rapport au début de la fenêtre sur le process cible.
Ø loc_count,remote_count: Nombres de variables à lire/écrire.
Ø loc_type,remote_type: Type des variables à lire/écrire.
Ø remote_rank: Numéro du process cible.
Ø win: fenêtre dans laquelle se fait la communication.
Il s'agit de communications non bloquantes ! Il faut vérifier la complétion par des
appel de synchronisation.
Il existe aussi MPI_ACCUMULATE.
Benoit Semelin 200
target=mod(rank+1,nb_proc)
index_to_put=local_computation()
CALL MPI_PUT(x(index_to_put),1,MPI_REAL,target,index_to_put,1,MPI_REAL,x_win,err)
CALL MPI_WIN_FENCE(MPI_MODE_NOSUCCEED,x_win,err)
CALL MPI_WIN_FREE(x_win,err)
MPI_WIN_FENCE est une fonction collective. Elle agit comme une barrière, aucun process
du groupe ne continue tant que toutes les communications de la fenêtre ne sont pas complétées.
Le premier argument sert à l'optimisation, il peut toujours valoir 0. Il peut prendre les valeurs:
- MPI_MODE_NOSTORE: pas d'écriture en local dans la fenêtre depuis le dernier FENCE.
- MPI_MODE_NOPUT: Pas de PUT vers la fenêtre locale d'ici le prochain FENCE.
- MPI_MODE_NOPRECEDE: Pas de communications antérieures à compléter.
- MPI_MODE_NOSUCCED: Pas de communications ultérieures.
On peut combiner ces valeurs (voir manuel de réference). Les optimisations correspondantes ne
sont parfois pas implémentées.
Il est possible de synchroniser les process 2 à 2 avec MPI_WIN_START,
MPI_WIN_COMPLETE, MPI_WIN_POST et MPI_WIN_WAIT.
Benoit Semelin 200
- Topologie de process
- Entrés-sorties parallèles
- Inter-communications
- Création dynamique de process
- Packing de données
GPU
Global Memory
PC I/O: 4 GB.s-1
Shared mem
Slow
Fast
Shared mem Shared mem
CU Local mem, reg
Ø OpenCL: librairies.
Tout neuf! 1 implementation?
- On définit une grille de block ( < 655353 ! mais si 30 multiprocesseurs, 30 blocs actifs)
Bloc identifié par: blockIdx.x, blockIdx.y, blockIdx.z (en C)
blockIdx%x, blockIdx%y, blockIdx%z (en Fortran)
Variables déclarés dans un kernel: mémoire locale du multiprocesseur (valeur ≠ pour chaque thread)
Accès à la mémoire globale: ~ qq centaines de cycles d’horloge
Accès à la mémoire partagé: ~1 cycle d’horloge
Resultat du Benchmark:
ü gcc –O3: 461 s
ü icc –O3: 227 s
ü pgf90 –fast: 309 s
ü ifort –O3: 38.8 s
ü nvcc, pgf90 -Mcuda : 3.67 s
- Les performances dépendent de:
§ Haute intensité arithmétique