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).
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 emploient la procédure.
standard : MPI choisit ou non de temporiser 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 (synchronisé) : 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 (bufferisé) : 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.
LAM se présente comme un simple démon présent sur chacune des machines. Chaque démon est structuré comme un micronoyau fournissant des services de passage de messages et de synchronisation. Certain processus internes au démon forment un sous-système de communication qui permet le passage de messages vers les démons des autres machines. La communication entre les différents processus se fait via UDP c'est à dire en mode non connecté et par paquets.
La communication se fait point à point : un message va d'un processus vers un autre sans nécessairement passer par le serveur. Les programmes écrits pour LAM sont compatibles avec les autres implémentations de MPI. LAM détecte les erreurs de communication dues à un « crash » d'une machine ou à une rupture de communication. LAM bloque toutes les communications vers la machine posant problème, les processus restants sont informés de la panne de manière asynchrone.
On peut donc gérer ce type d'erreurs dans lesapplications en supprimant les communications vers les processus ne réagissant plus et en créant de nouveaux.
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 :
Un code de retour fait partie des arguments, sa valeur (si différente de zéro) indique le type de problème rencontré.
Un envoi est bloquant signifie qu'il ne termine son action que lorsque les données envoyées ont été reçues et stockées par le destinataire de telle sorte que l'expéditeur peut accéder et modifier les valeurs en envoi.
La syntaxe d'un envoi bloquant standard est la suivante :
MPI_SEND(don, taille, dtype, dest, tag, comm, irc)Il s'agit de l'envoi d'un message identifié tag, de taille taille, de type dtype, à partir de l'adresse on, 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)Un envoi bloquant en mode buffered s'écrit :
MPI_BSEND (don, taille, dtype, dest, tag, comm, irc)Enfin, l'envoi bloquant en mode ready s'écrit :
MPI_RSEND (don, taille, dtype, dest, tag, comm, irc)La réception bloquante est faite de la manière suivante :
MPI_RECV (don, taille, dtype, source, tag, comm, status, irc)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 :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)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.
MPI_SENDRECV_REPLACE (don, taille, type, dest, sendtag, source, recvtag, comm, status, irc)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.
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)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 locale, 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)La syntaxe d'un envoi non bloquant synchronous est la suivante :
MPI_ISSEND (don, taille, dtype, dest, tag, comm, request, irc)Un envoi non bloquant en mode buffered s'écrit :
MPI_IBSEND (don, taille, dtype, dest, tag, comm, request, irc)Enfin, l'envoi non bloquant en mode ready s'écrit :
MPI_IRSEND (don, taille, dtype, dest, tag, comm, request, irc)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 connaître 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.
MPI_TEST (request, flag, status)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)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 entraîne 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 :
MPI_REQUEST_FREE (request)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 request persistant (initialisation) puis en utilisant cet objet request pour effectuer les communications. Cette construction permet de réduire l'overhead à chaque échange. Le requestassocié à la communication persistante est crée à l'aide d'une des quatre routines suivantes.
Création du request pour une communication persistante en mode standard :
MPI_SEND_INIT (don, taille, dtype, dest, tag, comm, request, irc)Création du request pour une communication persistante en mode buffered :
MPI_BSEND_INIT (don, taille, dtype, dest, tag, comm, request, irc)Création du request pour une communication persistante en mode synchronous :
MPI_SSEND_INIT (don, taille, dtype, dest, tag, comm, request, irc)Création du request pour une communication persistante en mode ready :
MPI_RSEND_INIT (don, taille, dtype, dest, tag, comm, request, irc)La réception persistante est initialisée par :
MPI_RECV_INIT (don, taille, dtype, source, tag, comm, request, irc)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)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.
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.
MPI_BARRIER (comm)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_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)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)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)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és
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)
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éfinis sont : somme, produit, maximum, minimum, indice du maximum, indice du minimum, etc ...
MPI_REDUCE (senddon, recvdon, taille, dtype, oper, dest, comm, irc)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 fournirait 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.
Ce tableau présente la liste des opérations de réduction prédéfinie :
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 |