Sunteți pe pagina 1din 11

Laborator 1

Introducere in MPI. Comunicatii punct la punct blocante 1. Introducere


Message Passing Interface (MPI) este un standard pentru comunicarea prin mesaje, elaborat de MPIForum. MPI are la baza modelul proceselor comunicante prin mesaje: un calcul este o colectie de procese secventiale care coopereaza prin comunicare de mesaje. MPI este o bibliotec nu un limbaj. El specifica conventii de apel pentru mai multe limbaje de programare: C, C++, FORTRAN.77, FORTRAN90. Initial, MPI a fost gandit ca un standard pentru arhitecturi de memorie distribuita:

insa cu timpul a fost adaptat si pentru scheme de tipul shared si hybrid memory:

2. Comunicarea punct la punct


2.1 Operatii de baza Operatiile de baza pentru comunicarea punct la punct sunt send si receive. Fiecare comunicare de mesaj se deruleaza intr-un anumit context. Mesajele sunt intotdeauna primite in contextul in care au fost transmise. Mesajele transmise in contexte diferite nu interfera. Contextul este partajat de un grup de procese. Procesele unui grup sunt ordonate si fiecare proces este identificat prin numrul sau de ordine din grup(rank). Pentru un grup de N procese, numerele valide au valori de la 0 la N-1.

Informaiile de context si de grup sunt asamblate ntr-un parametru suplimentar al operatiilor de comunicare, denumit comunicator. Mesajele poart, n afara datelor, informaii care permit diferenierea lor i recepia lor selectiv. Aceste informaii sunt: Sursa Destinatar Tag Comunicator si constituie plicul (anvelopa) mesajului. MPI prevede un comunicator predefinit, MPI_COMM_WORLD, care permite comunicarea cu orice proces accesibil dup iniializarea MPI si asocierea unui numar de ordine (rank) fiecrui proces, insa exista posibilitatea definirii de noi comunicatori. Cu aceste elemente, sintaxa operaiilor send si receive blocante este urmtoarea: MPI_SEND (buf, count, datatype, dest, tag, comm) unde parametrii sunt: IN buf, adresa initiala a tamponului sursa IN count, numrul de elemente (intreg ne-negativ) IN datatype, tipul fiecarui element IN dest, numrul de ordine al destinatarului (intreg) IN tag, tipul mesajului (intreg) IN comm, comunicatorul implicat MPI_RECV (buf, count, datatype, source, tag, comm, status) unde parametrii sunt: OUT buf, adresa initiala a tamponului destinatar IN count, numrul de elemente din tampon (intreg ne-negativ) IN datatype, tipul fiecrui element IN source, numarul de ordine al sursei (intreg) IN tag, tipul mesajului (intreg) IN comm, comunicatorul implicat OUT status, starea, element (structura) ce indica sursa, tipul i contorul mesajului efectiv primit In limbajul C aceste functii au urmatoarele prototipuri: int MPI_Send(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm); int MPI_Recv(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status); Operatia send este blocanta. Ea nu reda controlul apelantului pana cand mesajul nu a fost preluat din tamponul sursa, acesta din urma putand fi reutilizat de transmitator. Mesajul poate fi copiat direct n tamponul destinatar (daca se execut o operatie recv) sau poate fi salvat intr-un tampon temporar al sistemului. Memorarea temporar a mesajului decupleaz operatiile send i recv, dar este consumatoare de resurse. Alegerea unei variante apartine implementatorului MPI.

Si operatia recv este blocanta: controlul este redat programului apelant doar cand mesajul a fost receptionat. Exista si alte operatii MPI, care permit programatorului sa controleze modul de comunicare(blocant/non-blocant, cu/fara tampon temporar al sistemului).
EXEMPLUL 1 Hello World: # include mpi.h main (int argc, char **argv) { char message[40]; int myrank; MPI_Status status; MPI_Init (&argc, &argv); MPI_Comm_rank (MPI_COMM_WORLD, &myrank); if (myrank==0) { strcpy (message, Hello world); MPI_Send (message, strlen(message), MPI_CHAR, 1, 99, MPI_COMM_WORLD); } else { MPI_Recv (message, 20, MPI_CHAR, 0, 99, MPI_COMM_WORLD, &status); printf (\tReceived: %s \n, message); } MPI_Finalize(); }

In acest exemplu, procesul 0 (myrank==0) trimite un mesaj procesului 1 folosind operatia send. Procesul 1 (myrank!=0) primete acest mesaj prin operaia recv si il afiseaza. Descrierile celor doua procese coincid, iar diferentierea intre aciunile lor se face prin selectia dup numarul de ordine al procesului. Receptia unui mesaj este guvernata de valorile din anvelopa sa. Un mesaj este primit de destinatarul su dac anvelopa conine valorile specificate de operaia MPI_Recv pentru argumentele source, tag si comm. Receptorul poate specifica MPI_ANY_SOURCE, respectiv MPI_ANY_TAG pentru a accepta mesaje de la orice sursa si/sau cu orice tag. El nu poate specifica valori oarecare pentru comunicatorul comm !! Deci mesajul nu poate fi primit dect de receptorul adresat i pentru un acelai comunicator. In cazul folosirii unor valori ANY pentru sursa i tag, valorile corespunzatoare unui mesaj receptionat pot fi aflate din parametrul status. In limbajul C, status este o structur cu trei campuri, MPI_SOURCE, MPI_TAG, MPI_ERROR, care poate contine ins si campuri aditionale. Lungimea mesajului receptionat poate fi aflata prin apelul operatiei MPI_Get_count. MPI_Get_count(&status, datatype, &nelements); Functia se foloseste atunci cand numarul de elemente receptionate poate fi mai mic decat numarul specificat in MPI_Recv.

2.2 Alte operatii MPI Fiecare program MPI trebuie s contina un apel al operatiei MPI_Init. Rolul este de a initializa mediul n care programul va fi executat. Ea trebuie executata o singura data, naintea altor operaii MPI. Pentru a evita executia sa de mai multe ori (i deci provocarea unei erori), MPI ofer posibilitatea verificrii dac MPI_Init a fost sau nu executata. Acest lucru este facut prin MPI_INIATIALIZED (flag) OUT flag, este true dac MPI_Init a fost deja executata In limbajul C funcia are urmtorul prototip: int MPI_Initialized(int *flag); Aceasta este, de altfel, singura operaie ce poate fi executat nainte de MPI_Init. Revenind la MPI_Init, forma sa general este MPI_INIT() iar n limbajul C prototipul este int MPI_Init (int *argc, char ***argv); Functia C de initializare accepta ca argumente argc i argv, argumente ale funciei main, a caror utilizare nu este fixat de standard i depinde de implementare. Perechea functiei de initializare este MPI_Finalize, care trebuie executata de fiecare proces, pentru a nchide mediul MPI. Forma acestei operaii este MPI_FINALIZE() iar n limbajul C prototipul este: int MPI_Finalize (void); Utilizatorul trebuie s se asigure ca toate comunicatiile in curs de desfasurare s-au terminat inainte de apelul operatiei de finalizare. Dup MPI_Finalize, nici o alta operatie MPI nu mai poate fi executata (nici macar una de initializare). Un proces poate afla pozitia sa in grupul asociat unui comunicator prin apelul operatiei MPI_COMM_RANK (comm, rank) IN comm, este comunicatorul implicat OUT rank, este rangul procesului apelant In limbajul C funcia are urmtorul prototip: int MPI_Comm_rank(MPI_Comm comm, int *rank); 2.3 Un exemplu complet EXEMPLUL 2 - Serial Matrix Multiply:
#include <stdio.h> #include <stdlib.h> #define NRA 62 #define NCA 15 #define NCB 7 { int i, j, k; double a[NRA][NCA], /* misc */ /* matrix A to be multiplied */ /* number of rows in matrix A */ /* number of columns in matrix A */ /* number of columns in matrix B */

int main(int argc, char *argv[])

b[NCA][NCB], c[NRA][NCB];

/* matrix B to be multiplied */ /* result matrix C */

printf("Starting serial matrix multiple example...\n"); printf("Using matrix sizes a[%d][%d], b[%d][%d], c[%d][%d]\n", NRA,NCA,NCA,NCB,NRA,NCB); /* Initialize A, B, and C matrices */ printf("Initializing matrices...\n"); for (i=0; i<NRA; i++) for (j=0; j<NCA; j++) a[i][j]= i+j; for (i=0; i<NCA; i++) for (j=0; j<NCB; j++) b[i][j]= i*j; for(i=0;i<NRA;i++) for(j=0;j<NCB;j++) c[i][j] = 0.0; /* Perform matrix multiply */ printf("Performing matrix multiply...\n"); for(i=0;i<NRA;i++) for(j=0;j<NCB;j++) for(k=0;k<NCA;k++) c[i][j]+= a[i][k] * b[k][j]; printf("Here is the result matrix:"); for (i=0; i<NRA; i++) { printf("\n"); for (j=0; j<NCB; j++) printf("%6.2f ", c[i][j]); } printf ("\nDone.\n"); }

EXEMPLUL 3 - MPI Matrix Multiply


#include "mpi.h" #include <stdio.h> #include <stdlib.h>

#define NRA 62 #define NCA 15 #define NCB 7 #define MASTER 0

/* number of rows in matrix A */ /* number of columns in matrix A */ /* number of columns in matrix B */ /* taskid of first task */ /* setting a message type */ /* setting a message type */

#define FROM_MASTER 1 #define FROM_WORKER 2 int main (int argc, char *argv[]) { int numtasks, taskid, numworkers, source, dest, mtype, rows,

/* number of tasks in partition */ /* a task identifier */ /* number of worker tasks */ /* task id of message source */ /* task id of message destination */ /* message type */ /* rows of matrix A sent to each worker */

averow, extra, offset, /* used to determine rows sent to each worker */ i, j, k, rc; /* misc */ double a[NRA][NCA], b[NCA][NCB], c[NRA][NCB]; MPI_Status status; MPI_Init(&argc,&argv); MPI_Comm_rank(MPI_COMM_WORLD,&taskid); MPI_Comm_size(MPI_COMM_WORLD,&numtasks); if (numtasks < 2 ) { printf("Need at least two MPI tasks. Quitting...\n"); MPI_Abort(MPI_COMM_WORLD, rc); exit(1); } numworkers = numtasks-1; /**************************** master task ************************************/ if (taskid == MASTER) { printf("mpi_mm has started with %d tasks.\n",numtasks); printf("Initializing arrays...\n"); /* matrix A to be multiplied */ /* matrix B to be multiplied */ /* result matrix C */

for (i=0; i<NRA; i++) for (j=0; j<NCA; j++) a[i][j]= i+j; for (i=0; i<NCA; i++) for (j=0; j<NCB; j++) b[i][j]= i*j; /* Send matrix data to the worker tasks */ averow = NRA/numworkers; extra = NRA%numworkers; offset = 0; mtype = FROM_MASTER; for (dest=1; dest<=numworkers; dest++) { rows = (dest <= extra) ? averow+1 : averow; printf("Sending %d rows to task %d offset=%d\n",rows,dest,offset); MPI_Send(&offset, 1, MPI_INT, dest, mtype, MPI_COMM_WORLD); MPI_Send(&rows, 1, MPI_INT, dest, mtype, MPI_COMM_WORLD); MPI_Send(&a[offset][0], rows*NCA, MPI_DOUBLE, dest, mtype, MPI_COMM_WORLD); MPI_Send(&b, NCA*NCB, MPI_DOUBLE, dest, mtype, MPI_COMM_WORLD); offset = offset + rows; } /* Receive results from worker tasks */ mtype = FROM_WORKER; for (i=1; i<=numworkers; i++) { source = i; MPI_Recv(&offset, 1, MPI_INT, source, mtype, MPI_COMM_WORLD, &status); MPI_Recv(&rows, 1, MPI_INT, source, mtype, MPI_COMM_WORLD, &status); MPI_Recv(&c[offset][0], rows*NCB, MPI_DOUBLE, source, mtype, MPI_COMM_WORLD, &status); printf("Received results from task %d\n",source); } /* Print results */ printf("******************************************************\n"); printf("Result Matrix:\n");

for (i=0; i<NRA; i++) { printf("\n"); for (j=0; j<NCB; j++) printf("%6.2f ", c[i][j]); } printf("\n******************************************************\n"); printf ("Done.\n"); } /**************************** worker task ************************************/ if (taskid > MASTER) { mtype = FROM_MASTER; MPI_Recv(&offset, 1, MPI_INT, MASTER, mtype, MPI_COMM_WORLD, &status); MPI_Recv(&rows, 1, MPI_INT, MASTER, mtype, MPI_COMM_WORLD, &status); MPI_Recv(&a, rows*NCA, MPI_DOUBLE, MASTER, mtype, MPI_COMM_WORLD, &status); MPI_Recv(&b, NCA*NCB, MPI_DOUBLE, MASTER, mtype, MPI_COMM_WORLD, &status); for (k=0; k<NCB; k++) for (i=0; i<rows; i++) { c[i][k] = 0.0; for (j=0; j<NCA; j++) c[i][k] = c[i][k] + a[i][j] * b[j][k]; } mtype = FROM_WORKER; MPI_Send(&offset, 1, MPI_INT, MASTER, mtype, MPI_COMM_WORLD); MPI_Send(&rows, 1, MPI_INT, MASTER, mtype, MPI_COMM_WORLD); MPI_Send(&c, rows*NCB, MPI_DOUBLE, MASTER, mtype, MPI_COMM_WORLD); } MPI_Finalize(); }

2.4 Alte moduri de comunicare Modul standard de comunicare in MPI este blocant: controlul nu este redat apelantului pana cand mesajul nu a fost preluat din tamponul de emisie (intr-un tampon intermediar sau n tamponul receptorului), astfel ncat procesul transmitator poate sa modifice din nou tamponul de emisie. Memorarea temporar a mesajelor, in cazul unor operatii blocante, nu este ntotdeauna operaionala. Dac mesajele transmise sunt foarte lungi i sistemul nu dispune de suficient memorie temporar, ele trebuie transferate direct din tamponul sursa n tamponul destinatar. Aceasta presupune, insa, ca transmitatorul este blocat pan la executia unei recepii corespondente. In cazul unei combinaii de procese care-si transmit mesaje n lant, mecanismul conduce la degradarea performantelor, asa cum rezulta si din exemplul prezentat in figura:

Fiecare proces, cu exceptia primului si a ultimului, contine o pereche de operatii send-recv: prima transmite un mesaj catre urmatorul proces din secventa, a doua receptioneaz un mesaj de la precedentul proces. Transmiterea directa a datelor intre tampoanele de emisie i recepie secventializeaza executiile operatiilor de comunicare, procesele ramanand in asteptare perioade lungi de timp. Exista mai multe solutii pentru a controla executia operatiilor de comunicare astfel incat programul sa nu depinda de cantitatea de memorie tampon oferita de sistem. O solutie este imperecherea operatiilor, de exemplu, procesele de rang par execut operaiile in ordinea send-recv, in timp ce procesele de rang impar in ordine inversa, recv-send.

Schema precedenta este greu de implementat n cazul unei combinatii mai complicate de procese. O alternativa este utilizarea operaiei MPI_Sendrecv, care combin intr-un singur apel transmiterea unui mesaj catre o destinatie cu receptia unui mesaj de la un alt proces. Subsistemul

de comunicare se ocupa de combinarea operatiilor astfel incat s se evite blocarea definitiva a proceselor. Ambele operaii, send i recv, folosesc acelai comunicator, dar eventual diferite taguri. Tampoanele de emisie si de receptie trebuie s fie distincte. Operaia are si o varian, MPI_Sendrecv_replace, n care acelasi tampon este folosit atat pentru emisie cat si pentru receptie, astfel ca mesajul transmis este inlocuit cu mesajul receptionat. MPI permite programatorului sa prevada un tampon in care datele sunt plasate pana la livrarea lor. Operaia MPI_Bsend se termina, eventual inainte de demararea unei receptii corespondente, odata cu plasarea datelor in zona tampon. Programatorul poate rezerva aceasta zona prin operatia MPI_Buffer_attach, in care specifica dimensiunea dorita, suficient de mare pentru a pastra mesajele in tranzit. Cand tamponul nu mai este necesar, se foloseste MPI_Buffer_detach. Dupa detasare, utilizatorul poate reutiliza sau dealoca spatiul ocupat de tampon. Operaia de transmitere sincrona MPI_Ssend poate demara inaintea receptiei mesajului, dar se termina doar cand operatia de receptie corespondenta a inceput s preia mesajul transmis. Operatia de transmitere in modul pregatit, MPI_Rsend poate fi demarata numai daca receptia corespondenta a fost demarata anterior. MPI_Send - operatia send blocanta de baza. Reda controlul in momentul in care buffer-ul aplicatie folosit este liber. A se observa ca in acest caz redarea controlului depinde de implementare. In cazul in care este folosit un buffer sistem(tampon), redarea controlului este echivalenta cu momentul copierii buffer-ului aplicatie in buffer-ul sistem. MPI_Send (&buf,count,datatype,dest,tag,comm) MPI_recv - operatia receive blocanta de baza. Reda controlul in momentul in care s-a terminat receptionarea datelor in buffer-ul aplicatie. MPI_Recv (&buf,count,datatype,source,tag,comm,&status) MPI_Ssend - operatia send blocanta sincrona. Reda controlul in momentul in care buffer-ul aplicatie folosit este liber si receptorul a inceput procesul de primire a mesajului. MPI_Ssend (&buf,count,datatype,dest,tag,comm) MPI_Bsend - operatia send blocanta, cu buffer sistem. Permite copierea datelor aplicatie intr-un buffer sistem, special alocat si reda controlul in momentul incheierii copierii. Aceasta operatie trebuie folosita impreuna cu o operatie MPI_Buffer_attach. MPI_Bsend (&buf,count,datatype,dest,tag,comm)

MPI_Buffer_attach, MPI_Buffer_detach - Folosite pentru alocarea/dealocarea buffer-ului ce va fi folosit in MPI_Bsend. Un singur buffer poate fi atasat unui proces la un moment dat. MPI_Buffer_attach (&buffer,size) MPI_Buffer_detach (&buffer,size)

MPI_Rsend - ready send. Similara cu operatia send de baza, numai ca aceasta incepe doar in cazul in care operatia receive corespunzatoare este deja lansata, altfel, intoarce eroare. MPI_Rsend (&buf,count,datatype,dest,tag,comm)

MPI_Sendrecv - Permite trimiterea unui mesaj si lansarea unei operatii de receive inainte de blocare. Reda controlul in momentul in care buffer-ul aplicatie folosit de operatia send este liber si buffer-ul aplicatie de receive a primit mesajul corespunzator. MPI_Sendrecv(&sendbuf,sendcount,sendtype,dest,sendtag,&recvbuf,recvcount, recvtype,source,recvtag,comm,&status)

S-ar putea să vă placă și