Message Passing Interface
Description de la bibliothèque d'échanges de messages MPI
Introduction
Gestion de l'environnement
Communications points à points
Communications collectives
Gestion des groupes de processus
Structures de données
Tuning
Introduction
MPI (Message Passing Interface) est une bibliothèque d'échanges de messages pour machines parallèles homogènes. Une application MPI est un ensemble de processus exécutant chacun son propre code (modèle SPMD) et communiquant via des appels à des sous-programmes de la bibliothèque MPI. Le forum MPI est le site dédié à MPI, de même que ce lien vers Argonne ou encore le site netlib . Une aide pratique est le manuel décrivant ses fonctions et leur mise en oeuvre. Le modèle d'exécution d'une application MPI est le SPMD (Single Program Multiple Data), soit l'exécution du même programme pour tous les processus.
Gestion de l'environnement
Le sous-programme MPI_INIT initialise l'environnement nécessaire et MPI_FINALIZE désactive cet environnement. Les opérations effectuées par MPI portent sur des communicateurs, c'est-à-dire un groupe de processus, chacun possédant un rang, et un contexte de communication, soit un objet gérant les échanges point-à-point et collectif à l'aide d'étiquettes (tag) au sein de ce groupe. Les messages sont toujours reçus dans le contexte d'où ils sont émis et les messages envoyés dans différents contextes n'interfèrent pas.Le communicateur par défaut est MPI_COMM_WORLD, il comprend l'ensemble des processus actifs. Le nombre de processus gérés par un communicateur est fourni par le sous-programme MPI_COMM_SIZE. De même, le sous-programme MPI_COMM_RANK retourne le rang d'un processus (valeur comprise entre 0 et celle renvoyée par MPI_COMM_SIZE -1).
Exemple :
PROGRAM identificationC Initialisationinclude 'mpif.h'
INTEGER nbre_processus
INTEGER mon_rang
INTEGER irc
CALL MPI_INIT (irc)C Nombre de processusCALL MPI_COMM_SIZE (MPI_COMM_WORLD, nbre_processus, irc)C Mon rang dans le communicateurCALL MPI_COMM_RANK (MPI_COMM_WORLD, mon_rang, irc)C SortieWRITE (6, *) 'Mon rang est ', mon_rang, 'parmi ', nbre_processus
CALL MPI_FINALIZE (irc)STOP
END
Un peu de vocabulaire :
- non bloquant : une procédure rend la main avant que l'opération effectuée ne soit terminée et avant que l'utilisateur soit autorisé à ré-utiliser les ressources (comme les buffers) spécifiées dans l'appel.
- bloquant : lorsque la procédure rend la main, l'utilisateur peut ré-utiliser les ressources spécifiées dans l'appel.
- local : la complétion de l'opération ne dépend que du processus local. Une telle opération ne nécessite pas de communication avec un autre processus de l'utilisateur.
- non local : la complétion de l'opération dépend de l'exécution d'une procédure MPI avec un autre processus, par exemple une communication.
- collectif : tous les processus d'un groupe employent la procédure.
- standard : MPI choisit ou non de tamporiser le message en envoi (i.e. le copier dans une mémoire locale tampon). Si c'est le cas, l'envoi peut être terminé avant que la réception correspondante ne démarre. Dans le cas contraire, l'envoi ne se termine que si la réception a été postée et la donnée expédiée au destinataire. Le mode d'envoi standard est non local : la réussite de la communication peut dépendre de la présence de la réception correspondante.
- synchronous : l'envoi synchronous peut commencer même si la réception correspondante n'est pas amorcée. Cependant, il ne sera effectué que si cette réception a été postée et l'opération de réception du message commencée. La complétion d'une opération d'envoi synchronous indique seulement que la réception a atteint un certain point dans son processus.
- buffered : ce mode d'envoi peut démarrer et même finir sans que la réception ait été postée. Cette opération est alors locale puisqu'elle ne dépend pas de la réception correspondante.
- ready : un envoi qui utilise le mode de communication ready ne peut commencer que si la réception correspondante a déjà été postée. Dans le cas contraire, l'opération est erronée et les résultats faux.
La correspondance entre les types MPI et les types Fortran et C est fournie dans les tableaux suivants :
| Type MPI | Type Fortran |
|---|---|
| MPI_INTEGER | INTEGER |
| MPI_REAL | REAL |
| MPI_DOUBLE_PRECISION | DOUBLE PRECISION |
| MPI_COMPLEX | COMPLEX |
| MPI_LOGICAL | LOGICAL |
| MPI_CHARACTER | CHARACTER(1) |
| Type MPI | Type C |
|---|---|
| MPI_CHAR | signed char |
| MPI_SHORT | signed short int |
| MPI_INT | signed int |
| MPI_LONG | signed long int |
| MPI_UNSIGNED_CHAR | unsigned char |
| MPI_UNSIGNED_SHORT | unsigned short int |
| MPI_UNSIGNED | unsigned int |
| MPI_UNSIGNED_LONG | unsigned long int |
| MPI_FLOAT | float |
| MPI_DOUBLE | double |
| MPI_LONG_DOUBLE | long double |
Il est possible de mesurer le temps écoulé durant l'exécution d'une parite du code à l'aide de la function MPI_WTIME :
DOUBLE PRECISION MPI_WTIME ()
Exemple :
DOUBLE PRECISION tbeg, tend, ttot
DOUBLE PRECISION MPI_WTIME
EXTERNAL MPI_WTIMEtbeg = MPI_WTIME ()
... ...
... ...
tend = MPI_WTIME ()
ttot = tend - tbeg
Cette différence correspond au temps écoulé (exprimé comme un réel avec la seconde comme unité). La valeur retournée est locale au processus qui a fait ces appels.
Communications point-à-point
Une communication point-à-point a lieu entre deux processus, l'un est appelé émetteur, l'autre récepteur, identifiés par leur rang. Il s'agit de la communication de base de MPI et les opérations effectuées sont un send et un receive. Il existe différentes manières d'effectuer la communication. De manière générale, les paramètres nécessaires à sa construction sont :- les rangs des processus émetteur et récepteur,
- l'étiquette du message,
- le nom du communicateur qui définit le contexte de la communication,
- le type des données échangées,
- les données.
MPI_SEND (don, taille, dtype, dest, tag, comm, irc)<dtype> don(*) (IN) donnée (adresse initiale) à envoyer,
INTEGER taille (IN) nombre d'éléments à envoyer,
INTEGER dtype (IN) type MPI de chaque élément à envoyer,
INTEGER dest (IN) rang du processus destinataire,
INTEGER tag (IN) étiquette du message,
INTEGER comm (IN) communicateur,
INTEGER irc (OUT) code de retour.
Il s'agit de l'envoi d'un message identifié tag, de taille taille, de type dtype, à partir de l'adresse don, au processus dest, dans le communicateur comm.
La longueur du message est spécifiée en nombre d'éléments plutôt qu'en octets. Seul le code de retour est modifié par l'appel (il est en sortie, soit OUT).
Les trois derniers arguments (sans compter le code de retour) constituent l'enveloppe du message. Le destinataire est dest, un entier dont la valeur est comprise entre 0 et le nombre de processus -1. L'étiquette tag est un entier compris entre 0 et 32767 (au minimum); il permet de distinguer les types de messages. L'argument comm indique le communicateur utilisé pour l'envoi du message.
La syntaxe d'un envoi bloquant synchronous est la suivante :
MPI_SSEND (don, taille, dtype, dest, tag, comm, irc)<dtype> don(*) (IN) donnée (adresse initiale) à envoyer,
INTEGER taille (IN) nombre d'éléments à envoyer,
INTEGER dtype (IN) type MPI de chaque élément à envoyer,
INTEGER dest (IN) rang du processus destinataire,
INTEGER tag (IN) étiquette du message,
INTEGER comm (IN) communicateur,
INTEGER irc (OUT) code de retour.
Un envoi bloquant en mode buffered s'écrit :
MPI_BSEND (don, taille, dtype, dest, tag, comm, irc)<dtype> don(*) (IN) donnée (adresse initiale) à envoyer,
INTEGER taille (IN) nombre d'éléments à envoyer,
INTEGER dtype (IN) type MPI de chaque élément à envoyer,
INTEGER dest (IN) rang du processus destinataire,
INTEGER tag (IN) étiquette du message,
INTEGER comm (IN) communicateur,
INTEGER irc (OUT) code de retour.
Enfin, l'envoi bloquant en mode ready s'écrit :
MPI_RSEND (don, taille, dtype, dest, tag, comm, irc)<dtype> don(*) (IN) donnée (adresse initiale) à envoyer,
INTEGER taille (IN) nombre d'éléments à envoyer,
INTEGER dtype (IN) type MPI de chaque élément à envoyer,
INTEGER dest (IN) rang du processus destinataire,
INTEGER tag (IN) étiquette du message,
INTEGER comm (IN) communicateur,
INTEGER irc (OUT) code de retour.
La réception bloquante est faite de la manière suivante :
MPI_RECV (don, taille, dtype, source, tag, comm, status, irc)
<dtype> don(*) (OUT) donnée (adresse initiale) à recevoir,
INTEGER taille (IN) nombre d'éléments à recevoir,
INTEGER dtype (IN) type MPI de chaque élément à recevoir,
INTEGER source (IN) rang du processus expéditeur,
INTEGER tag (IN) étiquette du message,
INTEGER comm (IN) communicateur,
INTEGER status (MPI_STATUS_SIZE) (OUT) informations sur la réception,
INTEGER irc (OUT) code de retour.
Il s'agit de la réception d'un message identifié tag, de taille taille, de type dtype, placé à partir de l'adresse don, du processus source, dans le communicateur comm. L'argument supplémentaire status contient des informations sur la réception. Les constantes MPI_SOURCE, MPI_TAG et MPI_ERROR sont les indices de stockage dans status des champs source, étiquette et code d'erreur du message reçu. Il n'y a qu'une opération de réception qui peut être associée avec les quatre opérations d'envoi. Elle est bloquante et ne rend la main que lorsque la donnée est reçue.
Exemple :
PROGRAM send_recvC Initialisationinclude 'mpif.h'
INTEGER mon_rang
INTEGER irc
INTEGER tag
INTEGER status (MPI_STATUS_SIZE)REAL valeur
CALL MPI_INIT (irc)C Envoi
CALL MPI_COMM_RANK (MPI_COMM_WORLD, mon_rang, irc)
tag = 100IF (mon_rang == 0) THENC Réception
valeur = 10.0_rp
CALL MPI_SEND (valeur, 1, MPI_REAL, 1, tag, MPI_COMM_WORLD, irc)ELSEC Sortie
valeur = 0.0_rp
CALL MPI_RECV (valeur, 1, MPI_REAL, 0, tag, MPI_COMM_WORLD, status, irc)
WRITE (6, *) 'Processus rang ', mon_rang, ' a recu la valeur ', valeur
ENDIFCALL MPI_FINALIZE (irc)STOP
END
La sélection d'un message dans une opération de réception est réalisée à l'aide du contenu de l'enveloppe du message. Un message peut être reçu par une opération de réception si son enveloppe correspond aux arguments source, tag et comm spécifiés dans l'opération de réception. Le destinataire peut spécifier comme source MPI_ANY_SOURCE et/ou comme étiquette MPI_ANY_TAG pour indiquer que n'importe quelle source et étiquette sont acceptées. Le communicateur doit être le même que pour l'envoi.
Ainsi, un envoi doit spécifier son destinataire mais une réception peut accepter des messages d'un émetteur arbitraire.
L'échange des données entre deux processus permet d'effectuer une opération d'envoi et une opération de réception en une seule fois. Les deux processus peuvent être les mêmes. Cette opération est réalisée par MPI_SENDRECV. Dans le cas de send et receive bloquants, il faut faire attention à l'ordre des opérations pour éviter les deadlock, c'est-à-dire (par exemple) que tous les processus sont en même temps en attente de réception.
MPI_SENDRECV (senddon, sendtaille, sendtype, dest, sendtag, recvdon, recvtaille, recvtype, source, recvtag, comm, status, irc)<sendtype> senddon(*) (IN) donnée (adresse initiale) à envoyer,
INTEGER sendtaille (IN) nombre d'éléments à envoyer,
INTEGER sendtype (IN) type MPI de chaque élément à envoyer,
INTEGER dest (IN) rang du processus destinataire,
INTEGER sendtag (IN) étiquette de l'envoi,
<recvtype> recvdon(*) (OUT) donnée (adresse initiale) à recevoir,
INTEGER recvtaille (IN) nombre d'éléments à recevoir,
INTEGER source (IN) rang du processus expéditeur,
INTEGER recvtag (IN) étiquette de la réception,
INTEGER comm (IN) communicateur,
INTEGER status (MPI_STATUS_SIZE) (OUT) informations sur la réception,
INTEGER irc (OUT) code de retour.
Il s'agit d'un envoi et d'une réception bloquants. Ces deux opérations utilisent le même communicateur, mais des étiquettes pouvant être différentes. Les données à envoyer et recevoir doivent être disjointes mais peuvent avoir des types et des longueurs différentes. On peut aussi permuter des valeurs entre deux processus, à l'aide du MPI_SENDRECV_REPLACE.
Il s'agit d'un envoi et d'une réception bloquants. La même donnée est utilisée pour l'envoi et la réception de telle sorte que le message envoyé est remplacé par le message reçu. On peut augmenter les performances en faisant un recouvrement des communications par des calculs. On utilise pour cela des communications non bloquantes. Un send non bloquant initialise l'opération d'envoi mais ne la réalise pas. L'appel au sous-programme sera fini avant que le message ne soit parti. Une opération de vérification ultérieure est nécessaire pour s'assurer de la fin de l'envoi. Le transfert des données à partir de la mémoire de l'expéditeur peut être fait simultanément avec des calculs, après l'initialisation de l'envoi et avant sa complétion.MPI_SENDRECV_REPLACE (don, taille, type, dest, sendtag, source, recvtag, comm, status, irc)<sendtype> don(*) (INOUT) donnée (adresse initiale) à envoyer,
INTEGER taille (IN) nombre d'éléments à envoyer,
INTEGER type (IN) type MPI de chaque élément à envoyer,
INTEGER dest (IN) rang du processus destinataire,
INTEGER sendtag (IN) étiquette de l'envoi,
INTEGER source (IN) rang du processus expéditeur,
INTEGER recvtag (IN) étiquette de la réception,
INTEGER comm (IN) communicateur,
INTEGER status (MPI_STATUS_SIZE) (OUT) informations sur la réception,
INTEGER irc (OUT) code de retour.
Il en est de même pour une réception non bloquante. Un receive non bloquant initialise l'opération de réception mais ne la réalise pas. L'appel au sous-programme sera fini avant que le message ne soit reçu. Une opération de vérification ultérieure est nécessaire pour s'assurer de la réception effective du message. Le transfert des données vers la mémoire du destinataire peut être fait simultanément avec des calculs, après que la réception soit initialisée et avant sa complétion.
Les communications non bloquantes nécessitent un argument supplémentaire appelé request, qui permet d'identifier les opérations de communication et de faire correspondre l'opération qui initialise la communication avec elle qui la réalise effectivement. Un objet request contient différentes informations concernant l'opération de communication : le mode d'envoi, le contexte, l'étiquette, les arguments de destination pour un send, ou d'expédition pour un receive.
MPI_ISEND (don, taille, dtype, dest, tag, comm, request, irc)<dtype> don(*) (IN) donnée (adresse initiale) à envoyer,
INTEGER taille (IN) nombre d'éléments à envoyer,
INTEGER dtype (IN) type MPI de chaque élément à envoyer,
INTEGER dest (IN) rang du processus destinataire,
INTEGER tag (IN) étiquette du message,
INTEGER comm (IN) communicateur,
INTEGER request (OUT) identifiant de l'envoi,
INTEGER irc (OUT) code de retour.
Les envois non bloquants utilisent les mêmes quatre modes que les envois bloquants et ont le même sens. Dans tous les cas, l'initialisation de l'envoi est local, le sous-programme rend immédiatement la main. Enfin un envoi non bloquant peut être mis en correspondance avec une réception bloquante et vice-versa.
La syntaxe d'un envoi non bloquant standard est la suivante :
MPI_ISEND (don, taille, dtype, dest, tag, comm, request, irc)<dtype> don(*) (IN) donnée (adresse initiale) à envoyer,
INTEGER taille (IN) nombre d'éléments à envoyer,
INTEGER dtype (IN) type MPI de chaque élément à envoyer,
INTEGER dest (IN) rang du processus destinataire,
INTEGER tag (IN) étiquette du message,
INTEGER comm (IN) communicateur,
INTEGER request (OUT) identifiant de l'envoi,
INTEGER irc (OUT) code de retour.
La syntaxe d'un envoi non bloquant synchronous est la suivante :
MPI_ISSEND (don, taille, dtype, dest, tag, comm, request, irc)<dtype> don(*) (IN) donnée (adresse initiale) à envoyer,
INTEGER taille (IN) nombre d'éléments à envoyer,
INTEGER dtype (IN) type MPI de chaque élément à envoyer,
INTEGER dest (IN) rang du processus destinataire,
INTEGER tag (IN) étiquette du message,
INTEGER comm (IN) communicateur,
INTEGER request (OUT) identifiant de l'envoi,
INTEGER irc (OUT) code de retour.
Un envoi non bloquant en mode buffered s'écrit :
MPI_IBSEND (don, taille, dtype, dest, tag, comm, request, irc)<dtype> don(*) (IN) donnée (adresse initiale) à envoyer,
INTEGER taille (IN) nombre d'éléments à envoyer,
INTEGER dtype (IN) type MPI de chaque élément à envoyer,
INTEGER dest (IN) rang du processus destinataire,
INTEGER tag (IN) étiquette du message,
INTEGER comm (IN) communicateur,
INTEGER request (OUT) identifiant de l'envoi,
INTEGER irc (OUT) code de retour.
Enfin, l'envoi non bloquant en mode ready s'écrit :
MPI_IRSEND (don, taille, dtype, dest, tag, comm, request, irc)<dtype> don(*) (IN) donnée (adresse initiale) à envoyer,
INTEGER taille (IN) nombre d'éléments à envoyer,
INTEGER dtype (IN) type MPI de chaque élément à envoyer,
INTEGER dest (IN) rang du processus destinataire,
INTEGER tag (IN) étiquette du message,
INTEGER comm (IN) communicateur,
INTEGER request (OUT) identifiant de l'envoi,
INTEGER irc (OUT) code de retour.
La réception non bloquante est faite de la manière suivante :
MPI_IRECV (don, taille, dtype, source, tag, comm, request, irc)
Un appel non bloquant génère un objet associé à la communication, request, qui peut être utilisé pour connaitre le statut d'une communication (envoi ou réception) ou permettre d'attendre sa complétion. Pour cela on utilise respectivement les routines MPI_TEST et MPI_WAIT.<dtype> don(*) (OUT) donnée (adresse initiale) à recevoir,
INTEGER taille (IN) nombre d'éléments à recevoir,
INTEGER dtype (IN) type MPI de chaque élément à recevoir,
INTEGER source (IN) rang du processus expéditeur,
INTEGER tag (IN) étiquette du message,
INTEGER comm (IN) communicateur,
INTEGER request (OUT) identifiant de la réception,
INTEGER irc (OUT) code de retour.
MPI_TEST (request, flag, status)INTEGER request (INOUT) objet associé à la communication
LOGICAL flag (OUT) .TRUE. si la communication est effectuée
INTEGER status (MPI_STATUS_SIZE) (OUT) informations sur la communication
Un appel à MPI_TEST renvoie flag = .TRUE. si l'opération identifiée par request est achevée. Les valeurs en sortie des arguments request et status sont alors non définies. Dans le cas contraire, l'appel renvoie flag = .FALSE. . Dans ce dernier cas, la valeur de l'objet status est non définie. Cette fonction est locale.
MPI_WAIT (request, status)INTEGER request (INOUT) objet associé à la communication,
INTEGER status (MPI_STATUS_SIZE) (OUT) informations sur la communication.
Un appel à MPI_WAIT ne rend la main que lorsque l'opération identifiée par request est achevée. Cette fonction est non locale. Elle entraine la désallocation de l'objet request. Un objet peut aussi être libéré sans pour autant devoir attendre la complétion de la communication associée :
Si la communication est en cours, l'objet ne sera désalloué qu'après complétion. Il arrive parfois que l'on effectue une même communication un grand nombre de fois avec les mêmes arguments (même si les valeurs sont différentes), par exemple dans une boucle pour faire des mises à jour en fin d'itération. Dans un tel cas, il est possible d'optimiser la communication en associant ses arguments à un objet requestpersistant (initialisation) puis en utilisant cet objet request pour effectuer les communications. Cette construction permet de réduire l'overhead à chaque échange. Le request associé à la communication persistante est crée à l'aide d'une des quatres routines suivantes.MPI_REQUEST_FREE (request)INTEGER request (INOUT) objet associé à la communication.
Création du request pour une communication persistante en mode standard :
MPI_SEND_INIT (don, taille, dtype, dest, tag, comm, request, irc)<dtype> don(*) (IN) donnée (adresse initiale) à envoyer,
INTEGER taille (IN) nombre d'éléments à envoyer,
INTEGER dtype (IN) type MPI de chaque élément à envoyer,
INTEGER dest (IN) rang du processus destinataire,
INTEGER tag (IN) étiquette du message,
INTEGER comm (IN) communicateur,
INTEGER request (OUT) identifiant de la communication,
INTEGER irc (OUT) code de retour.
Création du request pour une communication persistante en mode buffered :
MPI_BSEND_INIT (don, taille, dtype, dest, tag, comm, request, irc)<dtype> don(*) (IN) donnée (adresse initiale) à envoyer,
INTEGER taille (IN) nombre d'éléments à envoyer,
INTEGER dtype (IN) type MPI de chaque élément à envoyer,
INTEGER dest (IN) rang du processus destinataire,
INTEGER tag (IN) étiquette du message,
INTEGER comm (IN) communicateur,
INTEGER request (OUT) identifiant de la communication,
INTEGER irc (OUT) code de retour.
Création du request pour une communication persistante en mode synchronous :
MPI_SSEND_INIT (don, taille, dtype, dest, tag, comm, request, irc)<dtype> don(*) (IN) donnée (adresse initiale) à envoyer,
INTEGER taille (IN) nombre d'éléments à envoyer,
INTEGER dtype (IN) type MPI de chaque élément à envoyer,
INTEGER dest (IN) rang du processus destinataire,
INTEGER tag (IN) étiquette du message,
INTEGER comm (IN) communicateur,
INTEGER request (OUT) identifiant de la communication,
INTEGER irc (OUT) code de retour.
Création du request pour une communication persistante en mode ready :
MPI_RSEND_INIT (don, taille, dtype, dest, tag, comm, request, irc)<dtype> don(*) (IN) donnée (adresse initiale) à envoyer,
INTEGER taille (IN) nombre d'éléments à envoyer,
INTEGER dtype (IN) type MPI de chaque élément à envoyer,
INTEGER dest (IN) rang du processus destinataire,
INTEGER tag (IN) étiquette du message,
INTEGER comm (IN) communicateur,
INTEGER request (OUT) identifiant de la communication,
INTEGER irc (OUT) code de retour.
La réception persistante est initialisée par :
MPI_RECV_INIT (don, taille, dtype, source, tag, comm, request, irc)
<dtype> don(*) (OUT) donnée (adresse initiale) à recevoir,
INTEGER taille (IN) nombre d'éléments à recevoir,
INTEGER dtype (IN) type MPI de chaque élément à recevoir,
INTEGER source (IN) rang du processus expéditeur,
INTEGER tag (IN) étiquette du message,
INTEGER comm (IN) communicateur,
INTEGER request (OUT) identifiant de la réception,
INTEGER irc (OUT) code de retour.
Le premier argument, don, est mis en sortie pour spécifier le droit d'écriture sur les données à recevoir. Ces appels n'engendrent pas de communication. Une communication persistante (envoi ou réception) qui utilise un request persistant est amorcée par la subroutine MPI_START :
MPI_START (request)INTEGER request (INOUT) identifiant de la communication
L'argument request est construit par un des cinq appels précédents et doit être inactif (c'est-à-dire inutilisé). Il devient actif une fois l'appel effectué. Cet appel est local avec des caractéristiques analogues à celles d'une communication non bloquante. Par exemple, un appel à MPI_START avec un request crée par MPI_SEND_INIT effectue la communication de la même manière qu'un appel à MPI_ISEND.
Communications collectives
Une communication collective est une communication impliquant un groupe de processus et réaliser ainsi en une fois une série de communications point-à-point avec possibilité d'effectuer une opération sur les données. Ces opérations se font au sein de communicateurs et n'ont pas besoin d'étiquette (tag). On peut classer les communications collectives en plusieurs catégories : synchronisation, transferts de données, transferts et opérations sur les données. La routine MPI_BARRIER bloque le processus appelant jusqu'à ce que tous les membres du groupe aient fait de même. Ensuite il rend la main.Il existe différents types de transferts, selon le nombre de processus émetteurs, récepteurs. Le diagramme suivant présente schématiquement les communications collectives :MPI_BARRIER (comm)INTEGER comm (IN) communicateur
MPI_BCAST (don, taille, dtype, source, comm, irc)<dtype> don(*) (INOUT) donnée (adresse initiale) à envoyer
INTEGER taille (IN) nombre d'éléments à envoyer,
INTEGER dtype (IN) type MPI de chaque élément à envoyer,
INTEGER source (IN) rang du processus émetteur,
INTEGER comm (IN) communicateur,
INTEGER irc (OUT) code de retour.
MPI_BCAST diffuse un message du processus source vers tous les processus du groupe, lui compris. Cet appel est effectué par tous les processus utilisant les mêmes arguments pour source et comm.
MPI_GATHER (senddon, sendtaille, sendtype, recvdon, recvtaille, recvtype, source, comm, irc)<sendtype> senddon(*) (IN) donnée (adresse initiale) à envoyer,
INTEGER sendtaille (IN) nombre d'éléments à envoyer,
INTEGER sendtype (IN) type MPI de chaque élément à envoyer,
<recvtype> recvdon(*) (OUT) donnée (adresse initiale) à recevoir,
INTEGER recvtaille (IN) nombre d'éléments à recevoir,
INTEGER recvtype (IN) type MPI de chaque élément à recevoir,
INTEGER source (IN) rang du processus destinataire,
INTEGER comm (IN) communicateur,
INTEGER irc (OUT) code de retour.
Dans un appel à MPI_GATHER, chaque processus (source inclus) envoie ses données au processus source qui les reçoit et les stocke dans l'ordre des rangs des processus. L'adresse de stockage de la réception est ignorée par tous les processus différents de source. Les arguments sendtaille et sendtype de tout processus émetteur doivent correspondre respectivement aux arguments recvtaille et recvtype du processus source. De plus les arguments source et comm doivent être les mêmes pour tous les processus.
La routine MPI_SCATTER effectue l'opération inverse de MPI_GATHER : le processus source distribue ses données par bloc entre les processus du groupe : le i-ème processus reçoit le i-ème bloc.
MPI_SCATTER (senddon, sendtaille, sendtype, recvdon, recvtaille, recvtype, source, comm, irc)<sendtype> senddon(*) (IN) donnée (adresse initiale) à envoyer,
INTEGER sendtaille (IN) nombre d'éléments à envoyer,
INTEGER sendtype (IN) type MPI de chaque élément à envoyer,
<recvtype> recvdon(*) (OUT) donnée (adresse initiale) à recevoir,
INTEGER recvtaille (IN) nombre d'éléments à recevoir,
INTEGER recvtype (IN) type MPI de chaque élément à recevoir,
INTEGER source (IN) rang du processus expéditeur,
INTEGER comm (IN) communicateur,
INTEGER irc (OUT) code de retour.
Les arguments sendtaille et sendtype du processus émetteur doivent correspondre respectivement aux arguments recvtaille et recvtype de tout processus récepteur. De plus les arguments source et comm doivent être les mêmes pour tous les processus.
MPI_ALLGATHER collecte des données réparties sur tous les processus et les leur diffusent ensuite. C'est un MPI_GATHER où tous les processus sont destinataires.
MPI_ALLGATHER (senddon, sendtaille, sendtype, recvdon, recvtaille, recvtype, comm, irc)<sendtype> senddon(*) (IN) donnée (adresse initiale) à envoyer,
INTEGER sendtaille (IN) nombre d'éléments à envoyer,
INTEGER sendtype (IN) type MPI de chaque élément à envoyer,
<recvtype> recvdon(*) (OUT) donnée (adresse initiale) à recevoir,
INTEGER recvtaille (IN) nombre d'éléments à recevoir,
INTEGER recvtype (IN) type MPI de chaque élément à recevoir,
INTEGER comm (IN) communicateur,
INTEGER irc (OUT) code de retour.
Les arguments sendtaille et sendtype d'un processus doivent correspondre aux arguments recvtaille et recvtype de tout autre processus.
Enfin, il est possible de faire des échanges croisées entre tous les processus à l'aide du MPI_ALLTOALL. C'est en quelque sorte une extension du MPI_ALLGATHER où chaque processus envoie des données distinctes à chacun des destinataires : le j-ème bloc envoyé par le processus i est reçu par le processus j comme le i-ème bloc dans recvdon. Les arguments sendtaille et sendtype d'un processus doivent correspondre aux arguments recvtaille et recvtype de tout autre processus.
MPI_ALLTOALL (senddon, sendtaille, sendtype, recvdon, recvtaille, recvtype, comm, irc)<sendtype> senddon(*) (IN) donnée (adresse initiale) à envoyer,
INTEGER sendtaille (IN) nombre d'éléments à envoyer,
INTEGER sendtype (IN) type MPI de chaque élément à envoyer,
<recvtype> recvdon(*) (OUT) donnée (adresse initiale) à recevoir,
INTEGER recvtaille (IN) nombre d'éléments à recevoir,
INTEGER recvtype (IN) type MPI de chaque élément à recevoir,
INTEGER comm (IN) communicateur,
INTEGER irc (OUT) code de retour.
Ces communications collectives ont des analogues (MPI_GATHERV, MPI_SCATTERV, MPI_ALLGATHERV, MPI_ALLTOALLV) pour travailler avec des données dont le nombre n'est pas le même pour tous les processus.
Une opération de réduction consiste à appliquer une même opération à un ensemble de valeurs réparties sur les processus pour en extraire une valeur unique qui sera envoyé à un (MPI_REDUCE) ou tous les processus (MPI_ALLREDUCE). Les opérations de réduction prédéfinies sont : somme, produit, maximum, minimum, indice du maximum, indice du minimum, etc ...MPI_REDUCE (senddon, recvdon, taille, dtype, oper, dest, comm, irc) <sendtype> senddon (*) (IN) donnée à réduire (adresse initiale),
<recvtype> recvdon (*) (OUT) résultat (adresse initiale),
INTEGER taille (IN) nombre d'éléments à recevoir,
INTEGER dtype (IN) type MPI de chaque élément à recevoir,
INTEGER oper (IN) opération de réduction,
INTEGER dest (IN) rang du processus destinataire,
INTEGER comm (IN) communicateur,
INTEGER irc (OUT) code de retour.
MPI_REDUCE combine les éléments figurant dans senddon de chaque processus du groupe à l'aide de l'opération de réduction oper et met le résultat dans recvdon du processus dest. Les données à réduire sont caractérisées par les arguments senddon, taille et dtype. Le résultat est caractérisé par les arguments recvdon, taille et dtype. Les deux ont donc le même nombre d'éléments de même type. L'appel est effectué par tous les membres du groupe et ont les mêmes arguments taille, dtype, oper, dest, comm. Dans le cas où chaque processus founit plus d'un élément, l'opération est d'abord effectuée sur cet ensemble.
L'opération oper est toujours supposée associative, les opérations prédéfinies sont aussi commutatives. Le type de données doit être compatible avec l'opération effectuée.
Le développeur peut construire ses propres opérations de réduction.
Le premier tableau présente la liste des opérations de réduction prédéfinies :
| Nom | Définition |
|---|---|
| MPI_MAX | maximum |
| MPI_MIN | minimum |
| MPI_SUM | somme |
| MPI_PROD | produit |
| MPI_LAND | .AND. logique |
| MPI_BAND | .AND. bit à bit |
| MPI_LOR | .OR. logique |
| MPI_BOR | .OR. bit à bit |
| MPI_LXOR | .XOR. logique |
| MPI_BXOR | .XOR. bit à bit |
| MPI_MAXLOC | maximum et indice |
| MPI_MINLOC | minimum et indice |
Le second tableau fournit les compatibilités entre les opérations et les types de données :
| oper | dtype |
| MPI_MAX, MPI_MIN | entier C, entier Fortran, flottant |
| MPI_SUM, MPI_PROD | entier C, entier Fortran, flottant, complexe |
| MPI_LAND, MPI_LOR, MPI_LXOR | entier C, booléen |
| MPI_BAND, MPI_BOR, MPI_BXOR | entier C, entier Fortran |
Avec les définitions suivantes :
-
entier C : MPI_INT, MPI_LONG, MPI_SHORT, MPI_UNSIGNED_SHORT, MPI_UNSIGNED, MPI_UNSIGNED_LONG
entier Fortran : MPI_INTEGER
flottant : MPI_FLOAT, MPI_DOUBLE, MPI_REAL, MPI_DOUBLE_PRECISION, MPI_LONG_DOUBLE
booléen : MPI_LOGICAL
complexe : MPI_COMPLEX
PROGRAM reduceC Initialisationinclude 'mpif.h'
INTEGER mon_rang
INTEGER irc
INTEGER tag
INTEGER status (MPI_STATUS_SIZE)REAL valeur, somme
CALL MPI_INIT (irc)C Valeurs
CALL MPI_COMM_RANK (MPI_COMM_WORLD, mon_rang, irc)IF (mon_rang .EQ. 0) THENC Réduction
valeur = 1000.0_rp
ELSE
valeur = REAL (mon_rang)
ENDIFCALL MPI_REDUCE (valeur, somme, 1, MPI_REAL, MPI_SUM, 0, MPI_COMM_WORLD, irc)C SortieIF (mon_rang == 0) WRITE (6,*) 'Somme globale : ', somme
CALL MPI_FINALIZE (irc)STOP
END
Les routines MPI_MINLOC et MPI_MAXLOC sont particulières puisqu'elles retournent deux valeurs. La première (respectivement la seconde) renvoie la valeur minimale (respectivement maximale) et le rang du processus qui possède ce minimum (respectivement maximum) global. L'opération qui définit MPI_MINLOC est la suivante :
{u, i} o {v, j} = {w, k} avec w = min (u,v) et k défini par :
i , si u < v
k = min (i,j) , si u = v
j , si u > v
Pour MPI_MAXLOC le principe est le même :
{u, i} o {v, j} = {w, k} avec w = max (u,v) et k défini par :
i , si u < v
k = min (i,j) , si u = v
j , si u > v
Ces deux opérations sont commutatives et associatives. Elles s'appliquent à des données fournies sous forme de paires : la valeur et l'indice. Pour résoudre le problème de type hétérogène en Fortran, des types prédéfinis sont fournis par MPI.
| Fortran | |
| Nom | Définition |
| MPI_2REAL | paire de real |
| MPI_2DOUBLE_PRECISION | paire de variables en double precision |
| MPI_2INTEGER | paire d'integer |
| C | |
| Nom | Définition |
| MPI_FLOAT_INT | couple float et int |
| MPI_DOUBLE_INT | couple double et int |
| MPI_LONG_INT | couple long et int |
| MPI_2INT | paire de variables int |
| MPI_SHORT_INT | couple short et int |
| MPI_LONG_DOUBLE_INT | couple long double et int |
Pour le Fortran, le type MPI_2REAL est analogue à la définition suivante d'un type dérivé (voir la section Structures de données) :
MPI_TYPE_CONTIGUOUS (2, MPI_REAL, MPI_2REAL)
Il en est de même pour les autres types Fortran. Pour le C, le type MPI_FLOAT_INT est défini par la suite d'instructions suivante :
type[0] = MPI_FLOAT
type[1] = MPI_INT
disp[0] = 0
disp[1] = sizeof(float)
block[0] = 1
block[1] = 1
MPI_TYPE_STRUCT (2, block, disp, type, MPI_FLOAT_INT)
Il en est de même pour les autres types C.
MPI fournit des variantes à chaque opération de réduction qui permettent une redistribution du résultat vers tous les processus du groupe : MPI_ALLREDUCE.
MPI_ALLREDUCE (senddon, recvdon, taille, dtype, oper, comm, irc)
<sendtype> senddon(*) (IN) donnée à réduire (adresse initiale),
<recvtype> recvdon(*) (OUT) résultat (adresse initiale),
INTEGER taille (IN) nombre d'éléments à recevoir,
INTEGER dtype (IN) type MPI de chaque élément à recevoir,
INTEGER oper (IN) opération de réduction,
INTEGER comm (IN) communicateur,
INTEGER irc (OUT) code de retour.
Il existe aussi des variantes qui distribuent le vecteur résultat à tous les processus du groupe : MPI_REDUCE_SCATTER.
MPI_REDUCE_SCATTER (senddon, recvdon, recvtaille, dtype, oper, comm, irc)<sendtype> senddon(*) (IN) donnée à réduire (adresse initiale),
<recvtype> recvdon(*) (OUT) résultat (adresse initiale),
INTEGER recvtaille(*) (IN) nombre d'éléments à envoyer, par processus,
INTEGER dtype (IN) type MPI de chaque élément à recevoir,
INTEGER oper (IN) opération de réduction,
INTEGER comm (IN) communicateur,
INTEGER irc (OUT) code de retour.
Cet appel effectue en premier une opération de réduction sur les éléments du vecteur défini par senddon, total = Somme ( recvtaille[i] ) et dtype. Ensuite, le vecteur des résultats est décomposé en n segments disjoints, ou n est le nombre de processus dans le groupe. Le segment i, formé de recvtaille[i] éléments, est envoyé au processus i et stocké dans recvdon.
Gestion des groupes de processus
Pour construire l'espace de communication, on utilise un communicateur qui est constitué :- d'un groupe de processus,
- d'un contexte de communication, i.e une propriété des communicateurs qui permet de partager l'espace de communication, gérer les communications point-à-point et collectives de telle sorte qu'elles n'interfèrent pas.
- à partir d'un autre communicateur,
- par l'intermédiaire d'un groupe de processus.
cette fonction partitionne le groupe associé à comm en sous-groupes disjoints, un pour chaque valeur de color. Chaque sous-groupe est formé des processus ayant la même valeur pour color, dont l'ordonnancement est donnée par l'argument key, sans lien avec leur rang dans le communicateur initial. Un nouveau communicateur est crée pour chaque sous-groupe et défini par new_comm.MPI_COMM_SPLIT (comm, color, key, new_comm, irc)INTEGER comm (IN) communicateur courant, à partager,
INTEGER color (IN) indicateur du communicateur d'appartenance,
INTEGER key (IN) indicateur du rang dans le sous-communicateur,
INTEGER new_comm (OUT) sous-communicateur,
INTEGER irc (OUT) code de retour.
Un tel procédé peut être utile lorsque l'on souhaite mettre en place une hiérarchie dans les calculs, comme pour une méthode multigrille.
La fonction de destruction d'un communicateur est MPI_COMM_FREE :
MPI_COMM_FREE (comm, irc)INTEGER comm (INOUT) communicateur à détruire,
INTEGER irc (OUT) code de retour.
Les routines de base dans un communicateur sont celles qui fournissent le nombre de processus qui le compose (MPI_COMM_SIZE) ainsi que le rang de chacun d'eux (MPI_COMM_RANK).
MPI_COMM_SIZE (comm, size, irc)INTEGER comm (IN) communicateur,
INTEGER size (OUT) nombre de processus du communicateur,
INTEGER irc (OUT) code de retour.
La démarche pour construire un communicateur à partir d'un groupe est un peu plus longue et nécessite de connaitre le groupe associé au communicateur par défaut MPI_COMM_WORLD.MPI_COMM_RANK (comm, rank, irc)INTEGER comm (IN) communicateur,
INTEGER rank (OUT) rang du processus appelant,
INTEGER irc (OUT) code de retour.
Un groupe est un ensemble ordonné de processus auquel sont associés des entiers appelés rangs. Ce sont des objets qui permettent la création de communicateurs. Un groupe est utilisé au sein d'un communicateur pour décrire les membres de l'environnement de communication et les ordonner.
En premier on détermine le groupe associé au communicateur MPI_COMM_WORLD, à l'aide de MPI_COMM_GROUP :
Ensuite, il faut construire le sous-groupe de ce groupe. Pour cela on doit au préalable définir un vecteur contenant les rang des processus qui formeront ce sous-groupe. Ensuite on utilise MPI_GROUP_INCL :MPI_COMM_GROUP (comm, group, irc)INTEGER comm (IN) communicateur,
INTEGER group (OUT) groupe associé au communicateur,
INTEGER irc (OUT) code de retour.
La fonction MPI_GROUP_INCL crée un groupe newgroup qui est formé de n processus du groupe group et de rang ranks[0], ranks[1], ..., ranks[n-1]. Le processus de rang i dans newgroup a pour rang ranks[i] dans group. Chacun des n éléments du vecteur ranks doit être un rang valide au sein de group et ils doivent être tous distincts.MPI_GROUP_INCL (group, n, ranks, newgroup, irc)INTEGER group (IN) groupe,
INTEGER n (IN) nombre de processus du sous-groupe et dimension du tableau suivant,
INTEGER ranks(*) (IN) rangs des processus formant le sous-groupe,
INTEGER newgroup (OUT) sous-groupe à construire,
INTEGER irc (OUT) code de retour.
Une fois le sous-groupe crée, on construit le communicateur associé à l'aide de MPI_COMM_CREATE :
MPI_COMM_CREATE (comm, newgroup, newcomm, irc)INTEGER comm (IN) communicateur,
INTEGER newgroup (IN) sous-groupe du groupe associé à comm,
INTEGER newcomm (OUT) nouveau communicateur associé à newgroup,
INTEGER irc (OUT) code de retour.
Il reste à libérer le sous-groupe crée avec MPI_GROUP_FREE :
MPI_GROUP_FREE (group, irc)INTEGER group (INOUT) groupe à détruire,
INTEGER irc (OUT) code de retour.
On dispose de fonctionnalités analogues à celles d'un communicateur pour déterminer le nombre d'éléments (MPI_GROUP_SIZE) et les rangs des processus (MPI_GROUP_RANK) membres du groupe :
MPI_GROUP_SIZE (group, size, irc)INTEGER group (IN) groupe,
INTEGER size (OUT) nombre de processus du groupe,
INTEGER irc (OUT) code de retour.
MPI_GROUP_RANK (group, rank, irc)INTEGER group (IN) groupe,
INTEGER rank (OUT) rang du processus appelant,
INTEGER irc (OUT) code de retour.
Structures de données
Dans les communications MPI, les données transmises sont typées. On dispose des type prédéfinis par MPI comme MPI_INTEGER, MPI_DOUBLE_PRECISION, MPI_SHORT_INT, etc ...On peut construire des structures de données plus complexes : homogènes (toutes les données sont du même type comme les éléments d'un tableau) ou hétérogènes (comme les structures en C ou les types dérivés en Fortran) pour effectuer les communications point-à-point.
- Types homogènes
- Valeurs contiguës (colonne de matrice en Fortran, ligne en C) : MPI_TYPE_CONTIGUOUS
newtype est le type de données obtenu en concaténant n variables contiguës de type oldtype.MPI_TYPE_CONTIGUOUS (n, oldtype, newtype, irc)INTEGER n (IN) nombre de valeurs,
INTEGER oldtype (IN) type de données prédéfini de chaque valeur,
INTEGER newtype (OUT) nouveau type de données,
INTEGER irc (OUT) code de retour
Exemple :
| 1 | 5 | 9 | 13 |
| 2 | 6 | 10 | 14 |
| 3 | 7 | 11 | 15 |
| 4 | 8 | 12 | 16 |
MPI_TYPE_CONTIGUOUS (4, MPI_INTEGER, type_col, irc)
- Valeurs distantes d'un pas constant (ligne ou bloc d'une matrice) : MPI_TYPE_VECTOR (pas donné en nombre d'éléments), MPI_TYPE_HVECTOR (pas donné en nombre d'octets)
MPI_TYPE_VECTOR (n, blocklength, stride, oldtype, newtype, irc)INTEGER n (IN) nombre de blocs,
INTEGER blocklength (IN) nombre d'éléments dans chaque bloc,
INTEGER stride (IN) nombre d'éléments entre chaque début de bloc,
INTEGER oldtype (IN) type de données prédéfini de chaque valeur,
INTEGER newtype (OUT) nouveau type de données,
INTEGER irc (OUT) code de retour.
MPI_TYPE_HVECTOR (n, blocklength, stride, oldtype, newtype, irc)INTEGER n (IN) nombre de blocs,
INTEGER blocklength (IN) nombre d'éléments dans chaque bloc,
INTEGER stride (IN) nombre d'octets entre chaque début de bloc,
INTEGER oldtype (IN) type de données prédéfini de chaque valeur,
INTEGER newtype (OUT) nouveau type de données,
INTEGER irc (OUT) code de retour.
Exemple :
| 1 | 5 | 9 | 13 |
| 2 | 6 | 10 | 14 |
| 3 | 7 | 11 | 15 |
| 4 | 8 | 12 | 16 |
MPI_TYPE_VECTOR (4, 1, 4, MPI_INTEGER, type_lig, irc)
Ce sont des constructeurs de types dont les données sont localisées dans des blocs uniformément espacés. Chaque bloc est obtenu en concaténant le même nombre de variables du type oldtype.
- Valeurs distantes d'un pas variable (triangle dans une matrice) : MPI_TYPE_INDEXED (pas donné en nombre d'éléments), MPI_TYPE_HINDEXED (pas donné en nombre d'octets)
MPI_TYPE_INDEXED (n, array_of_blocklengths, array_of_displacement, oldtype, newtype, irc)INTEGER n (IN) nombre de blocs et dimension des deux tableaux suivants,
INTEGER array_of_blocklengths (*) (IN) nombre d'éléments par bloc,
INTEGER array_of_displacements (*) (IN) espacements entre chaque bloc donné en multiples de oldtype,
INTEGER oldtype (IN) type de données prédéfini de chaque valeur,
INTEGER newtype (OUT) nouveau type de données,
INTEGER irc (OUT) code de retour.
MPI_TYPE_HINDEXED (n, array_of_blocklengths, array_of_displacements, oldtype, newtype, irc)INTEGER n (IN) nombre de blocs et dimension des deux tableaux suivants,
INTEGER array_of_blocklengths (*) (IN) nombre d'éléments dans chaque bloc,
INTEGER array_of_displacements (*) (IN) espacements entre chaque bloc donnés en octets,
INTEGER oldtype (IN) type de données prédéfini de chaque valeur,
INTEGER newtype (OUT) nouveau type de données,
INTEGER irc (OUT) code de retour.
Exemple :
oldtype
newtype :
n = 3
array_of_blocklengths = (2, 1, 3)
array_of_displacements = (0, 1, 3)
MPI_TYPE_INDEXED (5, array_of_blocklengths, array_of_displacements, oldtype, newtype, irc)
Ce sont des constructeurs de types dont les données sont localisées dans des blocs de longueur variable et d'espacement variable.
- Validation d'un type : MPI_TYPE_COMMIT
Un type créé par une des fonctions précédentes doit être validé avant toute utilisation au moyen d'un appel à MPI_TYPE_COMMIT :
MPI_TYPE_COMMIT (newtype, irc)INTEGER newtype (INOUT) type à valider,
INTEGER irc (OUT) code de retour.
Une fois cette validation effectuée, le nouveau type peut être utilisé comme tout type prédéfini dans des opérations de communication point-à-point.
- Libération d'un type : MPI_TYPE_FREE
MPI_TYPE_FREE (type, irc)INTEGER type (INOUT) type à détruire,
INTEGER irc (OUT) code de retour.
- Types hétérogènes
- définir une structure ou un type dérivé s,
- définir un tableau array_of_displacements(n) ou n est le nombre d'éléments de la structure,
- calculer l'adresse de chaque élément elem de la structure dans le tableau array_of_displacements, à l'aide de MPI_ADDRESS :
- calculer les déplacements relatifs à l'adresse de début de la structure :
- définir un tableau array_of_types(n) contenant le type de chaque élément,
- définir un tableau array_of_blocklengths(n) contenant la longueur de chaque élément en multiple du type de données associé,
- définir le type newtype à l'aide de MPI_TYPE_STRUCT:
- valider le nouveau type
MPI_ADDRESS (s%elem, array_of_displacements(i), irc)
MPI_TYPE_STRUCT (n, array_of_blocklengths, array_of_displacements, array_of_types, newtype, irc)INTEGER n (IN) nombre d'éléments de la structure et dimension des tableaux suivants,
INTEGER array_of_blocklengths(*) (IN) nombre d'éléments dans chaque bloc,
INTEGER array_of displacements(*) (IN) distance relative en octets au début de la structure,
INTEGER array_of_types(*) (IN) types des éléments dans chaque bloc,
INTEGER newtype (OUT) nouveau type de données,
INTEGER irc (OUT) code de retour.
MPI_COMMIT (newtype, irc)
Tuning
On peut optimiser les performances de son application de différentes manières.Lorsque l'on écrit le code, il faut garder à l'esprit que les communications font partie de ce que l'on appelle l'overhead, i.e. le surcout lié à la parallélisation, et donc que si la part des communications augmente cela fait baisser les performances. Il est donc souhaitable de limiter la taille des messages, le nombre de communications au strict minimum et de les synchroniser entre les processus. Il faut aussi répartir équitablement le travail entre ces derniers (load-balancing).
Voir les exemples pratiques de parallélisation d'EDP.
Ensuite il faut adapter le nombre de processus à la masse de calculs. Il ne sert à rien de lancer un code sur 40 processus, si dès 10 ou 12 processus les performances stagnent. Pour déterminer le "bon" nombre de processus, on fait quelques runs en augmentant le nombre de processus (par exemple des puissances de 2 : 2, 4, 8, ...) et en traçant la courbe x=nombre de processus, y = accélération ou efficacité du code.
Cela donne déjà une indication : lorsque le tracé obtenu fléchit, i.e. ce n'est plus une droite, on a atteint le nombre optimal de processus.
Enfin, on peut faire appel à des variables d'environnement pour indiquer au système d'exploitation la manière dont on veut faire exécuter le code.
Les variables d'environnement que l'on peut utiliser sont les suivantes :
setenv MP_SHARED_MEMORY yesMP_SHARED_MEMORY (indispensable !!) indique que les communications au sein des noeuds s'effectuent au travers de la mémoire (qui est partagée) et non pas au moyen de sockets ip.
setenv MP_WAIT_MODE poll
MP_WAIT_MODE permet de spécifier le comportement d'un processus lorsqu'il est en attente (de réception par exemple) : lemode poll est un mode où les processus avancenbt de concert ; le mode yield est un mode d'attente active, on peut aussi mettre sleep dans le cas de processus maitre qui ne font strictement rien afin d'éviter de bloquer un processeur inutilement et de consommer du temps cpu (qui sera par conséquent décompté de l'attribution).
ATTENTION CES VARIABLES D'ENVIRONNEMENT, ET D'AUTRES, SONT DEJA MISES EN PLACE SUR LE CLUSTERIBMDU CRIHAN; IL EST DONC INUTILE DE LES AJOUTER DANS VOS SCRIPTS DE SOUMISSION
Enfin il ne faut par oublier que l'état de la machine, l'activité du système influent sur les performances des applications parallèles (encore plus que pour les applications séquentielles) et par conséquent les temps de restitution en dépendent.
Exemple simple de programme MPI