38,757
edits
No edit summary |
(Updating to match new version of source page) |
||
Line 22: | Line 22: | ||
== Qu'est-ce que MPI? == | == Qu'est-ce que MPI? == | ||
<div class="mw-translate-fuzzy"> | |||
MPI (''message passing interface'') est en réalité une norme avec des sous-routines, fonctions, objets et autres éléments pour développer des programmes parallèles dans un environnement à mémoire distribuée. MPI est implémentée dans plusieurs bibliothèques, notamment Open MPI, MPICH et MVAPICH. La norme décrit la méthode d'appel en Fortran, C et C++, mais il existe aussi des méthodes indirectes d'appel pour plusieurs autres langages (Boost.MPI, mpi4py, Rmpi, etc.). | MPI (''message passing interface'') est en réalité une norme avec des sous-routines, fonctions, objets et autres éléments pour développer des programmes parallèles dans un environnement à mémoire distribuée. MPI est implémentée dans plusieurs bibliothèques, notamment Open MPI, MPICH et MVAPICH. La norme décrit la méthode d'appel en Fortran, C et C++, mais il existe aussi des méthodes indirectes d'appel pour plusieurs autres langages (Boost.MPI, mpi4py, Rmpi, etc.). | ||
</div> | |||
Puisque MPI est une norme ouverte sans droits exclusifs, un programme MPI peut facilement être porté sur plusieurs ordinateurs différents. Les programmes MPI peuvent être exécutés concurremment sur plusieurs cœurs à la fois et offrent une parallélisation efficace, permettant une bonne [[Scalability/fr|scalabilité]]. Puisque chaque processus possède sa propre plage mémoire, certaines opérations de débogage s'en trouvent simplifié es; en ayant des plages mémoire distinctes, les processus n’auront aucun conflit d’accès à la mémoire comme c'est le cas en mémoire partagée. Aussi, en présence d'une erreur de segmentation, le fichier ''core'' résultant peut être traité par des outils standards de débogage série. Le besoin de gérer la communication et la synchronisation de façon explicite donne par contre l'impression qu'un programme MPI est plus complexe qu'un autre programme où la gestion de la communication serait implicite. Il est cependant recommandé de restreindre les communications entre processus pour favoriser la vitesse de calcul d'un programme MPI. | Puisque MPI est une norme ouverte sans droits exclusifs, un programme MPI peut facilement être porté sur plusieurs ordinateurs différents. Les programmes MPI peuvent être exécutés concurremment sur plusieurs cœurs à la fois et offrent une parallélisation efficace, permettant une bonne [[Scalability/fr|scalabilité]]. Puisque chaque processus possède sa propre plage mémoire, certaines opérations de débogage s'en trouvent simplifié es; en ayant des plages mémoire distinctes, les processus n’auront aucun conflit d’accès à la mémoire comme c'est le cas en mémoire partagée. Aussi, en présence d'une erreur de segmentation, le fichier ''core'' résultant peut être traité par des outils standards de débogage série. Le besoin de gérer la communication et la synchronisation de façon explicite donne par contre l'impression qu'un programme MPI est plus complexe qu'un autre programme où la gestion de la communication serait implicite. Il est cependant recommandé de restreindre les communications entre processus pour favoriser la vitesse de calcul d'un programme MPI. | ||
Line 28: | Line 30: | ||
Nous verrons plus loin quelques-uns de ces points et proposerons des stratégies de solution; les références mentionnées au bas de cette page sont aussi à consulter. | Nous verrons plus loin quelques-uns de ces points et proposerons des stratégies de solution; les références mentionnées au bas de cette page sont aussi à consulter. | ||
<div class="mw-translate-fuzzy"> | |||
== Principes de base == | == Principes de base == | ||
Dans ce tutoriel, nous présenterons le développement d'un code MPI en C et en Fortran, mais les différents principes de communication s'appliquent à tout langage et à toute bibliothèque permettant une utilisation indirecte de l'API de MPI. Notre but ici est de paralléliser le programme simple "Hello World" utilisé dans les exemples. | Dans ce tutoriel, nous présenterons le développement d'un code MPI en C et en Fortran, mais les différents principes de communication s'appliquent à tout langage et à toute bibliothèque permettant une utilisation indirecte de l'API de MPI. Notre but ici est de paralléliser le programme simple "Hello World" utilisé dans les exemples. | ||
</div> | |||
<tabs> | <tabs> | ||
<tab name="C"> | <tab name="C"> | ||
Line 71: | Line 75: | ||
end program hello | end program hello | ||
}} | |||
</tab> | |||
<tab name="Python"> | |||
{{File | |||
|name=hello.py | |||
|lang="python" | |||
|contents= | |||
print('Hello, world!') | |||
}} | }} | ||
</tab> | </tab> | ||
Line 86: | Line 98: | ||
[[Image:SPMD_model.png|frame|center|'''Figure 3''': ''Contrôle de comportements divergents'']] | [[Image:SPMD_model.png|frame|center|'''Figure 3''': ''Contrôle de comportements divergents'']] | ||
<div class="mw-translate-fuzzy"> | |||
=== Cadre d'exécution === | === Cadre d'exécution === | ||
Un programme MPI importe le fichier entête approprié (<tt>mpi.h</tt> en C/C++; <tt>mpif.h</tt> en Fortran); il peut donc être compilé puis relié à l'implémentation MPI de notre choix. Dans la plupart des cas, l'implémentation possède un script pratique qui enveloppe l'appel au compilateur (''compiler wrapper'') et qui configure adéquatement <code>include</code> et <code>lib</code>, entre autres pour relier les indicateurs. Nos exemples utilisent les scripts de compilation suivants : | Un programme MPI importe le fichier entête approprié (<tt>mpi.h</tt> en C/C++; <tt>mpif.h</tt> en Fortran); il peut donc être compilé puis relié à l'implémentation MPI de notre choix. Dans la plupart des cas, l'implémentation possède un script pratique qui enveloppe l'appel au compilateur (''compiler wrapper'') et qui configure adéquatement <code>include</code> et <code>lib</code>, entre autres pour relier les indicateurs. Nos exemples utilisent les scripts de compilation suivants : | ||
Line 91: | Line 104: | ||
* pour le Fortran, <tt>mpif90</tt> | * pour le Fortran, <tt>mpif90</tt> | ||
* pour le C++, <tt>mpiCC</tt> | * pour le C++, <tt>mpiCC</tt> | ||
</div> | |||
Une fois les instances lancées, elles doivent se coordonner, ce qui se fait en tout premier lieu par l'appel d'une fonction d'initialisation : | Une fois les instances lancées, elles doivent se coordonner, ce qui se fait en tout premier lieu par l'appel d'une fonction d'initialisation : | ||
Line 108: | Line 122: | ||
MPI_INIT(IERR) | MPI_INIT(IERR) | ||
INTEGER :: IERR | INTEGER :: IERR | ||
</source> | |||
</tab> | |||
<tab name="Fortran 2008"> | |||
<source lang="fortran"> | |||
MPI_Init(ierr) | |||
INTEGER, OPTIONAL, INTENT(OUT) :: ierr | |||
</source> | |||
</tab> | |||
<tab name="Python (mpi4py)"> | |||
<source lang="Python"> | |||
# importing automatically initializes MPI with mpi4py | |||
MPI.Init() | |||
</source> | </source> | ||
</tab> | </tab> | ||
</tabs> | </tabs> | ||
<div class="mw-translate-fuzzy"> | |||
En C, les arguments de <code>MPI_Init</code> pointent vers les variables <code>argc</code> et <code>argv</code> qui sont les arguments en ligne de commande. Comme pour toutes les fonctions MPI en C, la valeur retournée représente l'erreur de la fonction. En Fortran, les routines MPI retournent l'erreur dans l'argument <code>IERR</code>. | En C, les arguments de <code>MPI_Init</code> pointent vers les variables <code>argc</code> et <code>argv</code> qui sont les arguments en ligne de commande. Comme pour toutes les fonctions MPI en C, la valeur retournée représente l'erreur de la fonction. En Fortran, les routines MPI retournent l'erreur dans l'argument <code>IERR</code>. | ||
</div> | |||
On doit aussi appeler la fonction <code>MPI_Finalize</code> pour faire un nettoyage avant la fin du programme, le cas échéant : | On doit aussi appeler la fonction <code>MPI_Finalize</code> pour faire un nettoyage avant la fin du programme, le cas échéant : | ||
Line 128: | Line 156: | ||
INTEGER :: IERR | INTEGER :: IERR | ||
</source> | </source> | ||
</tab> | |||
<tab name="Fortran 2008"> | |||
<source lang="fortran"> | |||
MPI_Finalize(ierr) | |||
INTEGER, OPTIONAL, INTENT(OUT) :: ierr | |||
</source> | |||
</tab> | |||
<tab name="Python (mpi4py)"> | |||
<source lang="Python"> | |||
# mpi4py installs a termination hook so there is no need to explicitly call MPI.Finalize. | |||
MPI.Finalize() | |||
</source> | |||
</tab> | </tab> | ||
</tabs> | </tabs> | ||
Line 176: | Line 216: | ||
program phello0 | program phello0 | ||
use mpi | |||
implicit none | |||
integer :: ierror | integer :: ierror | ||
Line 185: | Line 226: | ||
end program phello0 | end program phello0 | ||
}} | |||
</tab> | |||
<tab name="Fortran 2008"> | |||
{{File | |||
|name=phello0.f90 | |||
|lang="fortran" | |||
|contents= | |||
program phello0 | |||
use mpi_f08 | |||
implicit none | |||
call MPI_Init() | |||
print *, 'Hello, world!' | |||
call MPI_Finalize() | |||
end program phello0 | |||
}} | |||
</tab> | |||
<tab name="Python (mpi4py)"> | |||
{{File | |||
|name=phello0.py | |||
|lang="python" | |||
|contents= | |||
from mpi4py import MPI | |||
print('Hello, world!') | |||
}} | }} | ||
</tab> | </tab> | ||
Line 210: | Line 277: | ||
MPI_COMM_RANK(COMM, RANK, IERR) | MPI_COMM_RANK(COMM, RANK, IERR) | ||
INTEGER :: COMM, RANK, IERR | INTEGER :: COMM, RANK, IERR | ||
</source> | |||
</tab> | |||
<tab name="Fortran 2008"> | |||
<source lang="fortran"> | |||
MPI_Comm_size(comm, size, ierr) | |||
TYPE(MPI_Comm), INTENT(IN) :: comm | |||
INTEGER, INTENT(OUT) :: size | |||
INTEGER, OPTIONAL, INTENT(OUT) :: ierr | |||
MPI_Comm_rank(comm, rank, ierr) | |||
TYPE(MPI_Comm), INTENT(IN) :: comm | |||
INTEGER, INTENT(OUT) :: rank | |||
INTEGER, OPTIONAL, INTENT(OUT) :: ierr | |||
</source> | |||
</tab> | |||
<tab name="Python (mpi4py)"> | |||
<source lang="python"> | |||
MPI.Intracomm.Get_rank(self) | |||
MPI.Intracomm.Get_size(self) | |||
</source> | </source> | ||
</tab> | </tab> | ||
Line 269: | Line 356: | ||
program phello1 | program phello1 | ||
use mpi | |||
implicit none | |||
integer :: rank, size, ierror | integer :: rank, size, ierror | ||
Line 282: | Line 370: | ||
end program phello1 | end program phello1 | ||
}} | |||
</tab> | |||
<tab name="Fortran 2008"> | |||
{{File | |||
|name=phello1.f90 | |||
|lang="fortran" | |||
|contents= | |||
program phello1 | |||
use mpi_f08 | |||
implicit none | |||
integer :: rank, size | |||
call MPI_Init() | |||
call MPI_Comm_size(MPI_COMM_WORLD, size) | |||
call MPI_Comm_rank(MPI_COMM_WORLD, rank) | |||
print *, 'Hello from process ', rank, ' of ', size | |||
call MPI_Finalize(ierror) | |||
end program phello1 | |||
}} | |||
</tab> | |||
<tab name="Python (mpi4py)"> | |||
{{File | |||
|name=phello1.py | |||
|lang="python" | |||
|contents= | |||
from mpi4py import MPI | |||
comm = MPI.COMM_WORLD | |||
size = comm.Get_size() | |||
rank = comm.Get_rank() | |||
print('Hello from process %d of %d'%(rank, size)) | |||
}} | }} | ||
</tab> | </tab> | ||
Line 298: | Line 423: | ||
Pour compiler avec Boost : | Pour compiler avec Boost : | ||
[~]$ mpic++ --std=c++11 phello1.cpp -lboost_mpi-mt -lboost_serialization-mt -o phello1 | [~]$ mpic++ --std=c++11 phello1.cpp -lboost_mpi-mt -lboost_serialization-mt -o phello1 | ||
If you are using the Python version, you don't need to compile but can run with: | |||
[~]$ mpirun -np 4 python phello1.py | |||
=== Communication === | === Communication === | ||
Line 340: | Line 468: | ||
<type> MESSAGE(*) | <type> MESSAGE(*) | ||
INTEGER :: COUNT, DATATYPE, DEST, TAG, COMM, IERR | INTEGER :: COUNT, DATATYPE, DEST, TAG, COMM, IERR | ||
</source> | |||
</tab> | |||
<tab name="Fortran 2008"> | |||
<source lang="fortran"> | |||
MPI_Send(message, count, datatype, dest, tag, comm, ierr) | |||
TYPE(*), DIMENSION(..), INTENT(IN) :: message | |||
INTEGER, INTENT(IN) :: count, dest, tag | |||
TYPE(MPI_Datatype), INTENT(IN) :: datatype | |||
TYPE(MPI_Comm), INTENT(IN) :: comm | |||
INTEGER, OPTIONAL, INTENT(OUT) :: ierr | |||
</source> | |||
</tab> | |||
<tab name="Python (mpi4py)"> | |||
<source lang="python"> | |||
# For general Python objects (pickled) | |||
MPI.Intracomm.send(self, obj, int dest, int tag=0) | |||
# For numpy arrays (fast) | |||
MPI.Intracomm.Send(self, buf, int dest, int tag=0) | |||
</source> | </source> | ||
</tab> | </tab> | ||
Line 346: | Line 493: | ||
En Fortran :<tt>MPI_CHARACTER</tt>, <tt>MPI_INTEGER</tt>, <tt>MPI_REAL</tt>, etc. Pour la liste complète des types de données, consultez la section Références en bas de page. | En Fortran :<tt>MPI_CHARACTER</tt>, <tt>MPI_INTEGER</tt>, <tt>MPI_REAL</tt>, etc. Pour la liste complète des types de données, consultez la section Références en bas de page. | ||
<div class="mw-translate-fuzzy"> | |||
À la fonction de réception <tt>MPI_Recv</tt>, on ajoute l'argument <tt>status</tt> : en C, l'argument réfère à une structure allouée <tt>MPI_Status</tt> et en Fortran, l'argument contient une matrice <tt>MPI_STATUS_SIZE</tt> de nombres entiers. À son retour, <tt>MPI_Recv</tt> contiendra de l'information sur le message reçu. Nos exemples ne montrent pas cet argument, mais il doit faire partie des instructions. | À la fonction de réception <tt>MPI_Recv</tt>, on ajoute l'argument <tt>status</tt> : en C, l'argument réfère à une structure allouée <tt>MPI_Status</tt> et en Fortran, l'argument contient une matrice <tt>MPI_STATUS_SIZE</tt> de nombres entiers. À son retour, <tt>MPI_Recv</tt> contiendra de l'information sur le message reçu. Nos exemples ne montrent pas cet argument, mais il doit faire partie des instructions. | ||
</div> | |||
<tabs> | <tabs> | ||
<tab name="C"> | <tab name="C"> | ||
Line 376: | Line 525: | ||
<type> :: MESSAGE(*) | <type> :: MESSAGE(*) | ||
INTEGER :: COUNT, DATATYPE, SOURCE, TAG, COMM, STATUS(MPI_STATUS_SIZE), IERR | INTEGER :: COUNT, DATATYPE, SOURCE, TAG, COMM, STATUS(MPI_STATUS_SIZE), IERR | ||
</source> | |||
</tab> | |||
<tab name="Fortran 2008"> | |||
<source lang="fortran"> | |||
MPI_Recv(message, count, datatype, source, tag, comm, status, ierr) | |||
TYPE(*), DIMENSION(..) :: message | |||
INTEGER, INTENT(IN) :: count, source, tag | |||
TYPE(MPI_Datatype), INTENT(IN) :: datatype | |||
TYPE(MPI_Comm), INTENT(IN) :: comm | |||
TYPE(MPI_Status) :: status | |||
INTEGER, OPTIONAL, INTENT(OUT) :: ierr | |||
</source> | |||
</tab> | |||
<tab name="Python (mpi4py)"> | |||
<source lang="python"> | |||
# For general Python objects (pickled) | |||
MPI.Intracomm.recv(self, buf=None, int source=ANY_SOURCE, int tag=ANY_TAG, Status status=None) | |||
# For numpy arrays (fast) | |||
MPI.Intracomm.Recv(self, buf, int source=ANY_SOURCE, int tag=ANY_TAG, Status status=None) | |||
</source> | </source> | ||
</tab> | </tab> | ||
Line 462: | Line 631: | ||
implicit none | implicit none | ||
use mpi | |||
integer, parameter :: BUFMAX=81 | integer, parameter :: BUFMAX=81 | ||
character(len=BUFMAX) :: outbuf, inbuf, tmp | character(len=BUFMAX) :: outbuf, inbuf, tmp | ||
Line 491: | Line 660: | ||
end program phello2 | end program phello2 | ||
}} | }} | ||
</tab> | |||
<tab name="Fortran 2008"> | |||
{{File | |||
|name=phello2.f90 | |||
|lang="fortran" | |||
|contents= | |||
program phello2 | |||
implicit none | |||
use mpi_f08 | |||
integer, parameter :: BUFMAX=81 | |||
character(len=BUFMAX) :: outbuf, inbuf, tmp | |||
integer :: rank, num_procs | |||
integer :: sendto, recvfrom | |||
type(MPI_Status) :: status | |||
call MPI_Init() | |||
call MPI_Comm_rank(MPI_COMM_WORLD, rank) | |||
call MPI_Comm_size(MPI_COMM_WORLD, num_procs) | |||
outbuf = 'Hello, world! from process ' | |||
write(tmp,'(i2)') rank | |||
outbuf = outbuf(1:len_trim(outbuf)) // tmp(1:len_trim(tmp)) | |||
write(tmp,'(i2)') num_procs | |||
outbuf = outbuf(1:len_trim(outbuf)) // ' of ' // tmp(1:len_trim(tmp)) | |||
sendto = mod((rank + 1), num_procs) | |||
recvfrom = mod((rank + num_procs - 1), num_procs) | |||
call MPI_Send(outbuf, BUFMAX, MPI_CHARACTER, sendto, 0, MPI_COMM_WORLD) | |||
call MPI_Recv(inbuf, BUFMAX, MPI_CHARACTER, recvfrom, 0, MPI_COMM_WORLD, status) | |||
print *, 'Process', rank, ': Process', recvfrom, ' said:', inbuf | |||
call MPI_Finalize() | |||
end program phello2 | |||
}} | |||
</tab> | |||
<tab name="Python (mpi4py)"> | |||
{{File | |||
|name=phello2.py | |||
|lang="python" | |||
|contents= | |||
from mpi4py import MPI | |||
comm = MPI.COMM_WORLD | |||
rank = comm.Get_rank() | |||
size = comm.Get_size() | |||
outbuf = "Hello, world! from process %d of %d" % (rank, size) | |||
sendto = (rank + 1) % size; | |||
recvfrom = (rank + size - 1) % size; | |||
comm.send(outbuf, dest=sendto, tag=0) | |||
inbuf = comm.recv(source=recvfrom, tag=0) | |||
print('[P_%d] process %d said: "%s"]' % (rank, recvfrom, inbuf)) | |||
</tab> | </tab> | ||
</tabs> | </tabs> | ||
Line 659: | Line 887: | ||
implicit none | implicit none | ||
use mpi | |||
integer, parameter :: BUFMAX=81 | integer, parameter :: BUFMAX=81 | ||
Line 693: | Line 921: | ||
end program phello3 | end program phello3 | ||
}} | |||
</tab> | |||
<tab name="Fortran 2008"> | |||
{{File | |||
|name=phello3.f90 | |||
|lang="fortran" | |||
|contents= | |||
program phello3 | |||
implicit none | |||
use mpi_f08 | |||
integer, parameter :: BUFMAX=81 | |||
character(len=BUFMAX) :: outbuf, inbuf, tmp | |||
integer :: rank, num_procs | |||
integer :: sendto, recvfrom | |||
type(MPI_Status) :: status | |||
call MPI_Init() | |||
call MPI_Comm_rank(MPI_COMM_WORLD, rank) | |||
call MPI_Comm_size(MPI_COMM_WORLD, num_procs) | |||
outbuf = 'Hello, world! from process ' | |||
write(tmp,'(i2)') rank | |||
outbuf = outbuf(1:len_trim(outbuf)) // tmp(1:len_trim(tmp)) | |||
write(tmp,'(i2)') num_procs | |||
outbuf = outbuf(1:len_trim(outbuf)) // ' of ' // tmp(1:len_trim(tmp)) | |||
sendto = mod((rank + 1), num_procs) | |||
recvfrom = mod(((rank + num_procs) - 1), num_procs) | |||
if (MOD(rank,2) == 0) then | |||
call MPI_Send(outbuf, BUFMAX, MPI_CHARACTER, sendto, 0, MPI_COMM_WORLD) | |||
call MPI_Recv(inbuf, BUFMAX, MPI_CHARACTER, recvfrom, 0, MPI_COMM_WORLD, status) | |||
else | |||
call MPI_RECV(inbuf, BUFMAX, MPI_CHARACTER, recvfrom, 0, MPI_COMM_WORLD, status) | |||
call MPI_SEND(outbuf, BUFMAX, MPI_CHARACTER, sendto, 0, MPI_COMM_WORLD) | |||
endif | |||
print *, 'Process', rank, ': Process', recvfrom, ' said:', inbuf | |||
call MPI_Finalize() | |||
end program phello3 | |||
}} | |||
</tab> | |||
<tab name="Python (mpi4py)"> | |||
{{File | |||
|name=phello3.py | |||
|lang="python" | |||
|contents= | |||
from mpi4py import MPI | |||
comm = MPI.COMM_WORLD | |||
rank = comm.Get_rank() | |||
size = comm.Get_size() | |||
outbuf = "Hello, world! from process %d of %d" % (rank, size) | |||
sendto = (rank + 1) % size; | |||
recvfrom = ((rank + size) - 1) % size; | |||
if rank % 2 == 0: | |||
comm.send(outbuf, dest=sendto, tag=0) | |||
inbuf = comm.recv(source=recvfrom, tag=0) | |||
else: | |||
inbuf = comm.recv(source=recvfrom, tag=0) | |||
comm.send(outbuf, dest=sendto, tag=0) | |||
print('[P_%d] process %d said: "%s"]' % (rank, recvfrom, inbuf)) | |||
}} | }} | ||
</tab> | </tab> |