Personal tools
You are here: Home Calcul Technique Documentation IBM cluster p575 (Power5) Message Passing Interface
Document Actions

Message Passing Interface

Description de la bibliothèque d'échanges de messages MPI

Introduction

Gestion de l'environnement

Communications points à points

  • Communications bloquantes
  • Communications non bloquantes
  • Communications persistantes
  • Communications collectives

  • Synchronisation
  • Transferts
  • Réduction
  • 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 identification

    include 'mpif.h'

    INTEGER  nbre_processus
    INTEGER  mon_rang
    INTEGER  irc
     

    C Initialisation
    CALL MPI_INIT (irc)
    C Nombre de processus
    CALL MPI_COMM_SIZE (MPI_COMM_WORLD, nbre_processus, irc)
    C Mon rang dans le communicateur
    CALL MPI_COMM_RANK (MPI_COMM_WORLD, mon_rang, irc)

    WRITE (6, *) 'Mon rang est ', mon_rang, 'parmi ', nbre_processus
     

    C Sortie
    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.
    Il existe différents modes de communication avec MPI :
    • 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_WTIME

    tbeg = 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.


    flechehaut

    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.
    Un code de retour fait partie des arguments, sa valeur (si différente de zéro) indique le type de problème rencontré.
    • Communications bloquantes
    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)

    <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_recv

    include 'mpif.h'

    INTEGER  mon_rang
    INTEGER  irc
    INTEGER  tag
    INTEGER  status (MPI_STATUS_SIZE)

    REAL     valeur

    C Initialisation
    CALL MPI_INIT (irc)
    CALL MPI_COMM_RANK (MPI_COMM_WORLD, mon_rang, irc)
    tag = 100
    C Envoi
    IF (mon_rang == 0) THEN
       valeur = 10.0_rp
       CALL MPI_SEND (valeur, 1, MPI_REAL, 1, tag, MPI_COMM_WORLD, irc)
    C Réception
    ELSE
       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
    ENDIF
    C Sortie
    CALL 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.

    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 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.
    • Communications non bloquantes
    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)

    <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)
    <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.
    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.
    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 :

    MPI_REQUEST_FREE (request)

    INTEGER   request                   (INOUT)   objet associé à la communication.

    Si la communication est en cours, l'objet ne sera désalloué qu'après complétion.
    • Communications persistantes
    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.
    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.


    flechehaut

    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.
    • Synchronisation
    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)

    INTEGER   comm (IN)    communicateur

    • Transferts
    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 (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.

    • Réduction
    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
    Exemple :
    PROGRAM reduce

    include 'mpif.h'

    INTEGER  mon_rang
    INTEGER  irc
    INTEGER  tag
    INTEGER  status (MPI_STATUS_SIZE)

    REAL     valeur, somme

    C Initialisation
    CALL MPI_INIT (irc)
    CALL MPI_COMM_RANK (MPI_COMM_WORLD, mon_rang, irc)
    C Valeurs
    IF (mon_rang .EQ. 0) THEN
       valeur = 1000.0_rp
    ELSE
       valeur = REAL (mon_rang)
    ENDIF
    C Réduction
    CALL MPI_REDUCE (valeur, somme, 1, MPI_REAL, MPI_SUM, 0, MPI_COMM_WORLD, irc)
    C Sortie
    IF (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.


    flechehaut

    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.
    Il y a deux manières de construire un communicateur :
    • à partir d'un autre communicateur,
    • par l'intermédiaire d'un groupe de processus.
    Le communicateur par défaut est MPI_COMM_WORLD, il contient tous les processus et par conséquent les communications collectives les concernent tous. On peut créer des sous-ensembles de communication, i.e. partager le communicateur initial en plusieurs sous-communicateurs distincts mais de même nom. Pour cela on définit une valeur color associant à chaque procesus le numéro du communicateur auquel il appartiendra. Ensuite, on définit une valeur key permettant de numéroter les processus dans chaque communicateur. Enfin, on crée la partition où chaque communicateur aura le nom 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.

    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.
    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.

    MPI_COMM_RANK (comm, rank, irc)

    INTEGER      comm                 (IN)    communicateur,
    INTEGER      rank                 (OUT)   rang du processus appelant,
    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.
    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 :
    MPI_COMM_GROUP (comm, group, irc)

    INTEGER      comm                  (IN)    communicateur,
    INTEGER      group                 (OUT)   groupe associé au communicateur,
    INTEGER      irc                   (OUT)   code de retour.

    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_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.

    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.

    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.


    flechehaut

    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
    Ce sont des ensembles de données élémentaires de même type prédéfini mais leur distribution au sein du tableau peut être quelconque.
    • Valeurs contiguës (colonne de matrice en Fortran, ligne en C) : MPI_TYPE_CONTIGUOUS
    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

    newtype est le type de données obtenu en concaténant n variables contiguës de type oldtype.

    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
    Cette opération permet de désallouer un type construit (à la fin du code ou bien pour le redéfinir).
    MPI_TYPE_FREE (type, irc)

    INTEGER      type                   (INOUT)  type à détruire,
    INTEGER      irc                    (OUT)    code de retour.

    • Types hétérogènes
    La construction de types hétérogènes à l'aide de MPI_TYPE_STRUCT permet d'échanger des données de type struct en C et de type dérivé en Fortran90. C'est le constructeur le plus général fourni par MPI. Pour cela il faut :
    • 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 :
    • MPI_ADDRESS (s%elem, array_of_displacements(i), irc)
    • 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:
    • 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.

    • valider le nouveau type
    • MPI_COMMIT (newtype, irc)


    flechehaut

    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 yes
    setenv MP_WAIT_MODE poll
    MP_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.
    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


    flechehaut

    Powered by Plone CMS, the Open Source Content Management System

    This site conforms to the following standards: