web-dev-qa-db-fra.com

MPI: bloquant vs non bloquant

J'ai du mal à comprendre le concept de communication bloquante et non bloquante dans MPI. Quelles sont les différences entre les deux? Quels sont les avantages et les inconvénients?

57
lamba

Le blocage de la communication se fait à l'aide de MPI_Send() et MPI_Recv() . Ces fonctions ne reviennent pas (c'est-à-dire qu'elles se bloquent) tant que la communication n'est pas terminée. En simplifiant quelque peu, cela signifie que le tampon passé à MPI_Send() peut être réutilisé, soit parce que MPI l'a enregistré quelque part, soit parce qu'il a été reçu par la destination. De même, MPI_Recv() renvoie lorsque le tampon de réception a été rempli avec des données valides.

En revanche, la communication non bloquante se fait à l'aide de MPI_Isend() et MPI_Irecv() . Ces fonctions reviennent immédiatement (c'est-à-dire qu'elles ne se bloquent pas) même si la communication n'est pas encore terminée. Vous devez appeler MPI_Wait() ou MPI_Test() pour voir si la communication est terminée.

Le blocage de la communication est utilisé lorsqu'il est suffisant, car il est un peu plus facile à utiliser. La communication non bloquante est utilisée lorsque cela est nécessaire, par exemple, vous pouvez appeler MPI_Isend(), effectuer quelques calculs, puis faire MPI_Wait(). Cela permet aux calculs et à la communication de se chevaucher, ce qui conduit généralement à une amélioration des performances.

Notez que la communication collective (par exemple, tout réduire) n'est disponible que dans sa version de blocage jusqu'à MPIv2. IIRC, MPIv3 introduit une communication collective non bloquante.

Un aperçu rapide des modes d'envoi de MPI peut être vu ici .

79
user1202136

Ce message, bien qu'il soit un peu ancien, mais je conteste la réponse acceptée. la déclaration "Ces fonctions ne reviennent pas tant que la communication n'est pas terminée" est un peu erronée car le blocage des communications ne garantit aucune prise de contact n/b lors des opérations d'envoi et de réception.

Il faut d'abord savoir, l'envoi a quatre modes de communication: Standard, Buffered, Synchronous et Ready et chacun d'eux peut être bloquant et non bloquant

Contrairement à l'envoi, la réception n'a qu'un seul mode et peut être bloquant ou non bloquant.

Avant de poursuivre, il faut également être clair que je mentionne explicitement lequel est MPI_Send\Recv buffer et lequel est system buffer (qui est un tampon local dans chaque processeur appartenant à par la bibliothèque MPI utilisée pour déplacer les données parmi les rangs d'un groupe de communication)

BLOCKING COMMUNICATION: Le blocage ne signifie pas que le message a été remis au destinataire/destinataire. Cela signifie simplement que le tampon (d'envoi ou de réception) est disponible pour réutilisation. Pour réutiliser le tampon, il suffit de copier les informations dans une autre zone de mémoire, c'est-à-dire que la bibliothèque peut copier les données du tampon vers son propre emplacement de mémoire dans la bibliothèque, puis, par exemple, MPI_Send peut revenir.

La norme MPI indique très clairement de dissocier la mise en mémoire tampon des messages des opérations d'envoi et de réception. Un envoi bloquant peut se terminer dès que le message a été mis en mémoire tampon, même si aucune réception correspondante n'a été publiée. Mais dans certains cas, la mise en mémoire tampon des messages peut être coûteuse et, par conséquent, la copie directe du tampon d'envoi vers le tampon de réception peut être efficace. D'où MPI Standard fournit quatre modes d'envoi différents pour donner à l'utilisateur une certaine liberté dans le choix du mode d'envoi approprié pour son application. Jetons un coup d'œil à ce qui se passe dans chaque mode de communication:

1. Mode standard

En mode standard, il appartient à la bibliothèque MPI, de mettre en mémoire tampon ou non le message sortant. Dans le cas où la bibliothèque décide de mettre en mémoire tampon le message sortant , l'envoi peut se terminer avant même que la réception correspondante ait été invoquée. Dans le cas où la bibliothèque décide de ne pas mettre en mémoire tampon (pour des raisons de performances ou en raison de l'indisponibilité de l'espace tampon), l'envoi ne reviendra qu'après la publication d'une réception correspondante et les données dans le tampon d'envoi ont été déplacées vers le tampon de réception.

Ainsi MPI_Send en mode standard n'est pas local dans le sens où l'envoi en mode standard peut être démarré, qu'une réception correspondante ait été publiée ou non et que sa réussite puisse dépendre de l'occurrence d'une réception correspondante ( car il dépend de l'implémentation si le message sera mis en mémoire tampon ou non).

La syntaxe de l'envoi standard est ci-dessous:

int MPI_Send(const void *buf, int count, MPI_Datatype datatype, 
             int dest, int tag, MPI_Comm comm)

2. Mode tampon

Comme dans le mode standard, l'envoi en mode tampon peut être démarré indépendamment du fait qu'une réception correspondante a été publiée et l'envoi peut se terminer avant qu'une réception correspondante ait été publiée. Cependant, la principale différence provient du fait que si l'envoi est regardé et qu'aucune réception correspondante n'est publiée, le message sortant doit être mis en mémoire tampon. Notez que si la réception correspondante est publiée, l'envoi en mémoire tampon peut heureusement rencontrer le processeur qui a démarré la réception, mais en cas d'absence de réception, l'envoi en mode tampon doit mettre en mémoire tampon le message sortant pour permettre à l'envoi de se terminer. Dans son intégralité, un envoi tamponné est local. L'allocation de tampon dans ce cas est définie par l'utilisateur et en cas d'espace tampon insuffisant, une erreur se produit.

Syntaxe pour l'envoi de tampon:

int MPI_Bsend(const void *buf, int count, MPI_Datatype datatype,
             int dest, int tag, MPI_Comm comm)

. Mode synchrone

En mode d'envoi synchrone, l'envoi peut être démarré, qu'une publication correspondante ait été publiée ou non. Cependant, l'envoi ne se terminera avec succès que si une réception correspondante a été publiée et que le destinataire a commencé à recevoir le message envoyé par envoi synchrone. L'achèvement de l'envoi synchrone indique non seulement que le tampon de l'envoi peut être réutilisé, mais également le fait que le processus de réception a commencé à recevoir les données. Si l'envoi et la réception sont bloqués, la communication ne se termine pas à l'une ou l'autre extrémité avant le rendez-vous du processeur communicant.

Syntaxe pour l'envoi synchrone:

int MPI_Ssend(const void *buf, int count, MPI_Datatype datatype, int dest,
              int tag, MPI_Comm comm)

4. Mode prêt

Contrairement aux trois modes précédents, un envoi en mode prêt ne peut être démarré que si la réception correspondante a déjà été publiée. La fin de l'envoi n'indique rien sur la réception correspondante et indique simplement que le tampon d'envoi peut être réutilisé. Un envoi qui utilise le mode prêt a la même sémantique que le mode standard ou un mode synchrone avec les informations supplémentaires sur une réception correspondante. Un programme correct avec un mode de communication prêt peut être remplacé par un envoi synchrone ou un envoi standard sans effet sur le résultat en dehors de la différence de performances.

Syntaxe pour un envoi prêt:

int MPI_Rsend(const void *buf, int count, MPI_Datatype datatype, int dest, 
              int tag, MPI_Comm comm)

Après avoir traversé tous les 4 envois bloquants, ils peuvent sembler en principe différents, mais selon l'implémentation, la sémantique d'un mode peut être similaire à l'autre.

Par exemple, MPI_Send est en général un mode de blocage, mais selon la mise en œuvre, si la taille du message n'est pas trop grande, MPI_Send copiera le message sortant du tampon d'envoi vers le tampon système ('ce qui est généralement le cas dans le système moderne) et reviendra immédiatement. Regardons un exemple ci-dessous:

//assume there are 4 processors numbered from 0 to 3
if(rank==0){
    tag=2;
    MPI_Send(&send_buff1, 1, MPI_DOUBLE, 1, tag, MPI_COMM_WORLD);
    MPI_Send(&send_buff2, 1, MPI_DOUBLE, 2, tag, MPI_COMM_WORLD);
    MPI_Recv(&recv_buff1, MPI_FLOAT, 3, 5, MPI_COMM_WORLD);
    MPI_Recv(&recv_buff2, MPI_INT, 1, 10, MPI_COMM_WORLD);
}

else if(rank==1){
     tag = 10;
    //receive statement missing, nothing received from proc 0
    MPI_Send(&send_buff3, 1, MPI_INT, 0, tag, MPI_COMM_WORLD);
    MPI_Send(&send_buff3, 1, MPI_INT, 3, tag, MPI_COMM_WORLD);
}

else if(rank==2){
    MPI_Recv(&recv_buff, 1, MPI_DOUBLE, 0, 2, MPI_COMM_WORLD);
    //do something with receive buffer
}

else{ //if rank == 3
    MPI_Send(send_buff, 1, MPI_FLOAT, 0, 5, MPI_COMM_WORLD);
    MPI_Recv(recv_buff, 1, MPI_INT, 1, 10, MPI_COMM_WORLD);
}

Regardons ce qui se passe à chaque rang dans l'exemple ci-dessus

Rang essaie d'envoyer au rang 1 et au rang 2, et de recevoir du rang 1 et d 3.

Rang 1 essaie d'envoyer au rang 0 et au rang 3 et de ne rien recevoir des autres rangs

Rang 2 essaie de recevoir du rang 0 et plus tard effectuez une opération avec les données reçues dans le recv_buff.

Rang essaie d'envoyer au rang 0 et de recevoir du rang 1

Là où les débutants sont confus, c'est que le rang 0 envoie au rang 1 mais que le rang 1 n'a commencé aucune opération de réception, d'où la communication devrait bloquer ou bloquer et le la deuxième instruction d'envoi au rang 0 ne doit pas du tout être exécutée (et c'est ce que MPI souligne qu'il est défini par l'implémentation que le message sortant soit ou non mis en mémoire tampon). Dans la plupart des le système moderne, ces messages de petites tailles (ici la taille est 1) seront facilement mis en mémoire tampon et MPI_Send retournera et exécutera sa prochaine instruction MPI_Send. Par conséquent, dans l'exemple ci-dessus, même si la réception au rang 1 n'est pas démarrée, le 1er MPI_Send au rang 0 reviendra et exécutera sa prochaine instruction.

Dans une situation hypothétique où le rang 3 commence l'exécution avant le rang 0, il copiera le message sortant dans la première instruction d'envoi du tampon d'envoi vers un tampon système (dans un système moderne;)), puis commencera à exécuter son instruction de réception. Dès que le rang 0 termine ses deux instructions d'envoi et commence à exécuter son instruction de réception, les données mises en mémoire tampon dans le système par le rang 3 sont copiées dans le tampon de réception du rang 0.

Dans le cas où une opération de réception a commencé dans un processeur et qu'aucun envoi correspondant n'est publié, le processus se bloque jusqu'à ce que le tampon de réception soit rempli avec les données attendues. Dans cette situation, un calcul ou une autre communication MPI sera bloquée/arrêtée à moins que MPI_Recv ne soit revenu.

Après avoir compris les phénomènes de mise en mémoire tampon , il faut revenir et réfléchir davantage à MPI_Ssend qui a la véritable sémantique d'une communication bloquante. Même si MPI_Ssend copie le message sortant du tampon d'envoi vers un tampon système (qui est à nouveau défini par l'implémentation), il faut noter que MPI_Ssend ne reviendra pas à moins qu'un accusé de réception (au format de bas niveau) du processus de réception n'ait été reçu par le processeur d'envoi.

Heureusement MPI a décidé de faciliter les choses pour les utilisateurs en termes de réception et il n'y a qu'une seule réception dans la communication de blocage: MPI_Recv, et peut être utilisée avec n'importe lequel des quatre modes d'envoi décrits ci-dessus. Pour MPI_Recv, blocage signifie qui reçoit renvoie uniquement après qu'il contient les données dans sa mémoire tampon. Cela implique que la réception ne peut se terminer qu'après le démarrage d'un envoi correspondant, mais n'implique pas si ou non, il peut se terminer avant la fin de l'envoi correspondant.

Ce qui se passe lors de ces appels de blocage est que les calculs sont arrêtés jusqu'à ce que le tampon bloqué soit libéré. Cela entraîne généralement un gaspillage des ressources de calcul, car Send/Recv copie généralement les données d'un emplacement mémoire vers un autre emplacement mémoire, tandis que les registres dans le processeur restent inactifs.

COMMUNICATION NON BLOCANTE: Pour la communication non bloquante, l'application crée une demande de communication pour l'envoi et/ou la réception et récupère un descripteur puis se termine. C'est tout ce qui est nécessaire pour garantir que le processus est exécuté. C'est-à-dire que la bibliothèque MPI est avertie que l'opération doit être exécutée.

Pour le côté émetteur, cela permet un calcul chevauchant avec la communication.

Pour le côté récepteur, cela permet de chevaucher une partie de la surcharge de communication, c'est-à-dire de copier le message directement dans l'espace adresse du côté récepteur dans l'application.

32
ggulgulia

En utilisant la communication bloquante, vous devez faire attention à envoyer et recevoir des appels, par exemple regardez ce code

 if(rank==0)
 {
     MPI_Send(x to process 1)
     MPI_Recv(y from process 1)
 }
 if(rank==1)
 {
     MPI_Send(y to process 0);
     MPI_Recv(x from process 0);
 }

Que se passe-t-il dans ce cas?

  1. Le processus 0 envoie x au processus 1 et bloque jusqu'à ce que le processus 1 reçoive x.
  2. Le processus 1 envoie y au processus 0 et bloque jusqu'à ce que le processus 0 reçoive y, mais
  3. le processus 0 est bloqué de telle sorte que le processus 1 se bloque pour l'infini jusqu'à ce que les deux processus soient tués.
10
peaceman

C'est facile.

Le non-blocage signifie que le calcul et le transfert de données peuvent avoir lieu en même temps pour un seul processus.

Alors que le blocage signifie, mon pote, vous devez vous assurer que vous avez déjà terminé le transfert des données, puis revenir pour terminer la commande suivante, ce qui signifie que s'il y a un transfert suivi d'un calcul, le calcul doit être après le succès du transfert.