Pourquoi des communications non bloquantes? Illustration : mettez la main gauche dans le dos et passez une enveloppe avec la main droite à votre voisin de droite...deadlock! comment pourrait-on résoudre ce pb? C est le cas du ping pong vu précédemment... En cas de non tamponnage MPI_Send est un rendez vous. Donc si je programme comme suit: if (je suis le processus 0) {MPI_Send (.., 1,..) ; MPI_Recv(..., 1,..); else { MPI_Send(..., 0,...); MPI_Recv(..., 0,...): je me retrouve avec 2 processus bloqués indéfiniment : ils ne peuvent réaliser le rendez-vous car aucun n a pu faire le Recv! Par contre s ill y a tamponnage, le code fonctionne... Hugues Leroy Cours de DEA CH 3.4 / 1 Hugues Leroy Cours de DEA CH 3.4 / 2 Deadlock! (embrasse mortelle) 1 5 2 3 4 communicator 0 Tentative de résolution : inscrire le numéro du destinataire sur l enveloppe, la poser au milieu, puis prendre celle qui nous est destinée. Hugues Leroy Cours de DEA CH 3.4 / 3 Hugues Leroy Cours de DEA CH 3.4 / 4
Communications non bloquantes La communication se fait maintenant comme suit : Initialiser la communication non bloquante (on poste une demande) Aller faire autre un autre travail ( peut-être ce travail fera-t-il aussi d autres communications?) Attendre la fin de la communication non bloquante. En fait si je sais qu à un certain moment dans le futur proche que j aurais besoin d une donnée transmise par un autre processus, je peux anticiper en disant au système de communication de noter l information que je lui fournis dans ma demande (dans ce cas une réception non bloquante). Si un message du type spécifié (tag) et de l émetteur spécifié arrive, alors le système pourra le ranger directement dans le tampon que je lui ai indiqué. Lorsque j aurais besoin de la donnée, alors je ferais un Wait(). Si elle est arrivée, alors je serais bien parvenu à faire un recouvrement calcul/communication. Hugues Leroy Cours de DEA CH 3.4 / 5 Hugues Leroy Cours de DEA CH 3.4 / 6 Envoi non bloquant 1 5 2 in 3 4 communicator out 0 L émetteur poste une demande d envoi. Il devra tester l état de la requête avant de ré-utiliser le tampon. Hugues Leroy Cours de DEA CH 3.4 / 7 Hugues Leroy Cours de DEA CH 3.4 / 8
Réception non bloquante 1 5 2 in 3 4 communicator out 0 Le récepteur poste une demande de réception. Il ira tester ensuite si quelque chose est arrivé Hugues Leroy Cours de DEA CH 3.4 / 9 Hugues Leroy Cours de DEA CH 3.4 / 10 Handles datatype : idem comm. bloquantes ( MPI_Datatype ou INTEGER ) communicateur :... ( MPI_Comm ou INTEGER ) valeur permettant de tester l état de la communication ( request handle ) : MPI_Request ou INTEGER Le runtime de la bibliothèque MPI me fournira un handle (un descripteur) pour chaque communication non bloquante. Et ce handle me permettra de tester l état de la communication associée. Hugues Leroy Cours de DEA CH 3.4 / 11 Hugues Leroy Cours de DEA CH 3.4 / 12
Envois non bloquants synchrones C: int MPI_Issend (void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request)... int MPI_Wait (MPI_Request *request, MPI_Status *status) Fortran: MPI_ISSEND (buf, count, datatype, dest, tag,comm, handle, ierror)... MPI_WAIT (handle, status, ierror) Hugues Leroy Cours de DEA CH 3.4 / 13 Hugues Leroy Cours de DEA CH 3.4 / 14 Réception non bloquante C : int MPI_Irecv (void *buf, int count, MPI_Datatype datatype, int src, int tag,mpi_comm comm, MPI_Request *handle) int MPI_Wait (MPI_Request *handle, MPI_Status *status) Fortran : MPI_IRECV (buf, count, datatype, src, tag,comm, handle, ierror) MPI_WAIT (handle, status, ierror) Hugues Leroy Cours de DEA CH 3.4 / 15 Hugues Leroy Cours de DEA CH 3.4 / 16
bloquant ou non bloquant? Les envois et les réceptions peuvent être bloquants ou non bloquants. On peut combiner envoi bloquant et réception non bloquante (et vice-versa) Les envois non bloquants peuvent utiliser n importe quel mode : synchrone, tamponné, standard ou ready Le mode synchrone affecte uniquement la terminaison de l opération. Attention, les communications non bloquantes consomment des ressources mémoire : l émetteur doit conserver un descripteur pour la requête, le récepteur doit aussi pouvoir tester l enveloppe. Les envois Bsend bloquants demandent aussi plus de mémoire et impliquent des tamponnages intermédiaires, alors que les comm. non bloquantes demandent au programmeur d utiliser plus de fonctions MPI... Attention aux hot spots (ex: un seul processus devant recevoir une floppée de messages ) Hugues Leroy Cours de DEA CH 3.4 / 17 Hugues Leroy Cours de DEA CH 3.4 / 18 Communications non bloquantes mode Envoi standard Envoi synchrone Envoi tamponné (Buffered) Ready send Réception MPI MPI_Isend MPI_Issend MPI_Ibsend MPI_Irsend MPI_Irecv Ce sont les mêmes modes que pour les comm. bloquantes : on a juste un grand I avant le nom de la fonction. Send devient Isend, Recv -> Irecv,... et un paramètre de plus (le handle de type MPI_Request) Hugues Leroy Cours de DEA CH 3.4 / 19 Hugues Leroy Cours de DEA CH 3.4 / 20
Terminaison on peut attendre la fin de l opération ou tester son état : C: int MPI_Wait (MPI_Request *handle, MPI_Status *status) int MPI_Test (MPI_Request *handle, int *flag, MPI_Status *status) flag vaudra vrai si la communication testée est terminée Fortran: MPI_WAIT (handle, status, ierror) MPI_TEST (handle, flag, status, ierror) Hugues Leroy Cours de DEA CH 3.4 / 21 Hugues Leroy Cours de DEA CH 3.4 / 22 Exercice 3 : communications sur un anneau Un ensemble de processus est sur un anneau virtuel : Chaque processus conserve (dans un entier) son rang dans MPI_COMM_WORLD. Chaque processus passe cet entier à son voisin de droite. Puis reçoit un entier de son voisin de gauche. Il continue à passer cette information jusqu à ce qu il reçoive de nouveau son rang. Chaque processus calcule la somme des rangs (somme de tout ce qui passe) On utilisera des communications bloquantes. Modifier le programme pour utiliser des communications non bloquantes. 0 1 3 2 Cet exercice doit être développé comme il est dit dans l énoncé! Vous pourrez simplement vérifier que votre résultat est correct en le comparant avec la formule adhoc : n*(n+1)/2 Dans la suite de ce cours, nous ferons le même exercice en utilisant une topologie virtuelle, ou une opération de réduction. Hugues Leroy Cours de DEA CH 3.4 / 23 Hugues Leroy Cours de DEA CH 3.4 / 24
Communications multiples Avec un seul appel de fonction MPI, on peut tester l état ou attendre la fin d une ou de plusieurs communications. int MPI_Testall(int count, MPI_Request array_of_requests[], int *flag, MPI_Status *array_of_statuses ) int MPI_Testsome (...) int MPI_Testany (...) int MPI_Waitall(...) int MPI_Waitany(...) int MPI_Waitsome(...) Pour le manuel d utilisation des diverses fonctions, on peut aussi commander sous Unix (ou Linux) : man -M /home/mpi/man MPI_Waitany... consultez le manuel! par ex: commande mpiman -netscape pour mpich Hugues Leroy Cours de DEA CH 3.4 / 25 Hugues Leroy Cours de DEA CH 3.4 / 26 Communications multiples Wait Test au moins un Waitany Testany retourne un seul tous Waitall Testall au moins un retourne tous ceux qui ont fini Waitsome Testsome Hugues Leroy Cours de DEA CH 3.4 / 27 Hugues Leroy Cours de DEA CH 3.4 / 28
Exemple : multiples producteurs / un seul consommateur typedef struct{ char data[maxsize]; int datasize; MPI_Request req; Buffer; Buffer buffer[]; MPI_Status status; Pn-1... MPI_Comm_rank(comm, &rank); MPI_Comm_size(comm,&size); if (rank!= size-1) { /* producteur */ buffer=(buffer *) malloc (sizeof(buffer)); while ( 1 ) { /* boucle inficie */ produire (buffer->data,&buffer->datasize); MPI_Send ( buffer->data,buffer->datasize,mpi_char,size-1,tag,comm); else { /* rank == size-1 : le consommateur */ buffer=(buffer *) malloc (sizeof(buffer)*(size-1)); for (i=0;i<size-1;i++) MPI_Irecv ( buffer[i].data,maxsize, MPI_CHAR, i, tag, comm, &(buffer[i].req)); for (i=0; ; i=(i+1)%(size-1) ) { /* PREMIERE POSSIBILITE */ MPI_Wait ( &(buffer[i].req), &status); MPI_Get_count ( &status, MPI_CHAR, &(buffer[i].datasize)); consommer ( buffer[i].data, buffer[i]->datasize); MPI_Irecv (buffer[i].data, MAXSIZE, MPI_CHAR, i, tag, comm, &(buffer[i].req)); P0 P1 P2 Pn-2 Un consommateur : le processus de numéro n-1, et n-1 producteurs (de 0 à n-2). L exploration séquentielle de la boucle (marquée par la main) montre que tant que le processus i n a pas tranmis son message, alors on reste bloqué sur le Wait. Donc si un processus produit 2 fois plus que les autres, on ne pourra pas le traiter en priorité. Dès qu on a traité sa demande, alors on repart dans la boucle sur i, et on ne reviendra à lui que lorsqu on aura traité (attendu) toutes les autres demandes. Une alternative est de faire un MPI_Test sur la requête du processus courant (i). MPI_test rend vrai ou faux suivant que la communication en question est finie ou pas. Hugues Leroy Cours de DEA CH 3.4 / 29 Hugues Leroy Cours de DEA CH 3.4 / 30 /* DEUXIEME POSSIBILITE ( version plus élégante ), la 1ère version ne traitant pas de façon équitable tous les producteurs car il y a une séquentialisation dans le traitement */ i=0; while (1) { for (flag=0;! flag; i=(i+1)%(size-1) ) MPI_Test (&(buffer[i].req), &flag, &status); MPI_Get_count ( &status, MPI_CHAR, &(buffer[i].datasize)); consommer ( buffer[i].data, buffer[i]->datasize); MPI_Irecv (buffer[i].data, MAXSIZE, MPI_CHAR, i, tag, comm, &(buffer[i].req)); Ici on a un meilleur traitement des demandes, et on ne consomme plus comme précédemment au rythme du plus lent des producteurs. remarquez comment la boucle sur MPI_Test permet de sélectionner une requête! (on sort lorsque flag vaut vrai) Hugues Leroy Cours de DEA CH 3.4 / 31 Hugues Leroy Cours de DEA CH 3.4 / 32
Remarques Le processus inexistant est noté MPI_PROC_NULL une communication vers ce processus retourne tout de suite. Un message reçu de ce processus sera de longueur 0 et aura l étiquette MPI_ANY_TAG Intérêt : (cf Jacobi sur domaine 2D maillé) on pourra utiliser le même appel MPI pour les communications d échange des bords, les processus étant ou non sur les bords de la grille La requête inexistante est MPI_REQUEST_NULL On a la garantie que toutes les communications vers le processus MPI_PROC_NULL terminent. Idem pour les Recv. On verra que cette propriété va nous permettre de rendre le code source plus lisible lorsqu on utilisera les topologies virtuelles. On n aura plus les fastidieux tests du genre: si je suis un processus sur le bord gauche alors je reçois et envoie à droite, sinon si je suis un processus sur le bord droit alors je reçoit et envoie à gauche, sinon (je suis un processus au milieu) je reçoit/envoie à gauche et je reçoit/envoie à droite. Hugues Leroy Cours de DEA CH 3.4 / 33 Hugues Leroy Cours de DEA CH 3.4 / 34 Fonctions avancées int MPI_Probe ( int source, int tag, MPI_Comm comm, MPI_Status *status) permet de choisir une enveloppe avant d en lire le contenu l appel non bloquant est MPI_Iprobe (int source, int tag, int *flag, MPI_Comm comm, MPI_Status *status). flag prendra la valeur vrai ou faux suivant l état de la communication testée. Communications persistantes (non bloquantes, donc il faut tester ou attendre la fin avant de réutiliser les tampons) int MPI_Send_init ( void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request) int MPI_Start ( MPI_Request *request) ou int MPI_Startall ( int count, MPI_Request array_of_requests[] ) int MPI_Request_free ( MPI_Request *request) supprimer une requête ( attention! si le message est déjà parti ou a été reçu..?!??..) int MPI_Cancel ( MPI_Request *request ) int MPI_Test_cancelled ( MPI_Status *status, int *flag ) Hugues Leroy Cours de DEA CH 3.4 / 35 Hugues Leroy Cours de DEA CH 3.4 / 36
Intérêt des communications persistantes Dans un programme il arrive qu il y ait une boucle sur un envoi et une réception de message, où seules les données échangées varient, mais ni leurs adresses en mémoire, ni leur nombre, ni leurs types (regardez par exemple le programme ping pong). On va donc créer un schéma de communication une fois pour toutes, et dans la boucle on activera la requête. À la fin de la boucle, on pourra libérer la requête. Ce schéma va permettre de masquer la latence de communication et de réduire les surcoûts induits par les appels aux fonctions de communications dans la boucle. On n aura plus non plus la recopie des paramètres. boucle {... ; a=... ; MPI_Send ( &a, 1, MPI_INT,...); peut devenir : MPI_Send_init (&a, 1, MPI_INT,...,&req); boucle {...; a=...; MPI_Start( &req);... MPI_Request_free(&req); mode standard synchrone tamponné réception MPI MPI_Send_init MPI_Ssend_init MPI_Bsend_init MPI_Recv_init Hugues Leroy Cours de DEA CH 3.4 / 37 Hugues Leroy Cours de DEA CH 3.4 / 38 Remarquons les équivalences : MPI_Irecv(...,&req) avec MPI_Recv_init (...&req); MPI_Start(&req); MPI_Wait (&req) avec MPI_Wait(&req); MPI_Request_free(&req); Exercice 4 Bien mesurer les latences et débits obtenus. Après cet exercice vous pourrez savoir quelles sont les primitives de communications les plus efficaces : Send/Recv, Sendrecv, Sendrecv_replace, Send_init/Start, etc... Remarque: nous n aurons pas les mêmes indications suivant les architectures matérielles et les implémentations de MPI utilisées. Reprendre le ping pong et remplacer les envois/réceptions pour en faire des communications persistantes. Hugues Leroy Cours de DEA CH 3.4 / 39 Hugues Leroy Cours de DEA CH 3.4 / 40
Utilisation de MPI_Probe int MPI_Probe (int source, int tag, MPI_Comm comm, MPI_Status *status ) #define MCW MPI_COMM_WORLD double eleven[500]; double data[500]; MPI_Init(&argc,&argv); MPI_Comm_rank(MCW,&rank); if ( rank == 1 ) { for (i=0; i<50;i++) {eleven[i]=11.0; MPI_Send(eleven,50,MPI_DOUBLE,0,1001,MCW); else { MPI_Recv(data,500,MPI_DOUBLE,MPI_ANY_SOURCE,MPI_ANY_TAG,MCW, &status); MPI_Get_count(&status,MPI_DOUBLE,&count); printf( Process %d received %d doubles\n,rank,count); Qui y a-t-il de mal fait dans ce code? le récepteur ne sait pas combien d éléments il va recevoir => gaspillage de mémoire (500 au lieu de 50) ne connait pas le TAG (pourrait être utilisé comme un signal) ne connait pas l émetteur (pas important ici, mais on pourrait avoir un traitement différent suivant l émetteur du message) Solution : utiliser MPI_Probe et l allocation dynamique de mémoire Hugues Leroy Cours de DEA CH 3.4 / 41 Hugues Leroy Cours de DEA CH 3.4 / 42 double eleven[500]; double *data; MPI_Init(&argc,&argv); MPI_Comm_rank(MCW,&rank); if ( rank == 1 ) { for (i=0; i<50;i++) {eleven[i]=11.0; MPI_Send(eleven,50,MPI_DOUBLE,0,1001,MCW); else { MPI_Probe(1,MPI_ANY_TAG,MCW,&status); MPI_Get_count(&status,MPI_DOUBLE,&count); MPI_Type_size(MPI_DOUBLE,&n); data=(double *) calloc (count,n); MPI_Recv(data, count, MPI_DOUBLE, 1, MPI_ANY_TAG, MCW, &status); Hugues Leroy Cours de DEA CH 3.4 / 43