Documente Academic
Documente Profesional
Documente Cultură
Iai 2005
Constructori pentru tipuri de date ................................................. 87 Utilizarea tipurilor de date derivate .............................................. 92 Lungimea mesajelor ........................................................................ 93 Topologii pentru procese ..................................................................... 94 Topologii virtuale ............................................................................. 95 Instructiuni de instalare, compilare i execuie a aplicaiilor care folosesc biblioteca MPI .................................................................. 103
Prefa
Intenia autorilor acestei lucrri a fost de a oferi studenilor de la Facultatea de Automatic si Calculatoare ansamblul de informaii necesare realizrii lucrrilor de laborator aferente disciplinei Algoritmi i Limbaje de Calcul Paralel. Lucrarea se adreseaz ns tuturor celor care doresc iniierea n calculul paralel. Atunci cnd au fost selectate temele abordate s-a considerat c este cunoscut coninutul cursului Algoritmi i Limbaje de Calcul paralel sau al crii Algoritmi pentru prelucrri paralale, Editura Gh. Asachi, Iai, 2002 , autor Mitic Craus. Autorii mulumesc studenilor masteranzi Raluca Macovei, Roxana Ungureanu i Ionu Vasiliu, pentru contribuia lor la realizarea seciunii Comunicri colective .
Comprimarea (Reducerea)
Fie M o mulime de n = 2m elemente, M = {ai | i = 1,...,n} Mr. Mulimea M urmeaz a fi procesat pentru a calcula valoarea a1...an unde este o lege de compoziie asociativ definit pe Mr. Mulimea de referin Mr poate fi R iar poate fi +,min,max, etc. Pentru simplitate se presupune c datele de intrare sunt iniial memorate ntr-un tablou unidimensional A[0..n-1] iar n este de forma n = 2d.
Exemplu de comprimare
Complexitatea
La terminarea execuiei algoritmului A[1] = a1...an. Complexitatea timp paralel a algoritmului de comprimare (implementat pe o main CREW-PRAM sau pe o ahitectur VLSI de tip arbore binar) este O(logn). Aceasta deoarece adncimea arborelui de calcul este logn. Numrul procesoarelor utilizate este p n/2.
n/p-1+logp uniti de timp. Dac p = [n/ logn] atunci n/p-1+logp 2logn-1-loglogn O(logn).
Complexitatea
In final, valorile A[n]...A[n+j], j = 0,...,n-1 vor fi memorate n locaiile B[n+j] ale tabloului B[1..2n-1]. Timpul de execuie este de O(logn). Numrul procesoarelor utilizate este O(n). Reducerea numrului procesoarelor se poate face printr-o tehnic asemntoare celei descrise la reducere. 11
Exemplu
Operaia nu admite element simetric proc calcul_prefixe_gen(A,B,) begin for k = m-1 down to 0 do for all j:2k j 2k+1-1 par do A[j]=A[2j]A[2j+1]; for k = 0 to m do for all j:2k j 2k+1-1 par do if j = 2k then B[j]=A[j] else if bit0(j) = 1 then B[j]=B[[(j-1)/2]] else B[j]=B[[(j-2)/2]]A[j]); end 12
Complexitatea
Timpul de execuie = O(logn) Numrul de procesoare = O([n/logn])
Dublarea (scurtcircuitarea)
Fie L o list simplu nlnuit format din n elemente. Fiecare element k L are asociat un numar val[k] i un pointer p[k]. Se presupune c fiecrui element k L i este asignat un procesor Pk.
Algoritm de scurtcircuitare
proc scurtcircuitare(L,value,) begin repeat logn times for all k:k L par do if p[k] p[p[k]] then val[k] = val[k]val[p[k]]; p[k] = p[p[k]] end Obs: este o operaie binar oarecare.
Complexitatea
Complexitatea timp a algoritmului de scurtcircuitare (implementat pe o main CREW-PRAM) este O(logn).
13
Vizualizare
Comunicri colective
Comunicrile colective pot fi clasificate astfel: - unu-la-toi: difuzare comunicare personalizat - toi-la-toi difuzare comunicare personalizat - toi-la-unu
Difuzarea unu-la-toi
Un procesor trimite acelai mesaj M la toi ceilali:
15
unde ts este timpul necesar iniializrii unui transfer iar tw este timpul necesar transferului unei componente atomice a mesajului M de la un nod la unul din vecinii si.
16
Aa cum se poate observa din figur, procesul are 3 faze de comunicare. Se poate de asemenea observa c ordinea n care sunt alese dimensiunile pentru comunicare nu afecteaz rezultatul. In planificarea ce corespunde figurii, comunicarea ncepe de-a lungul celei mai mari dimensiuni ( ce corespunde bitului cel mai semnificativ al reprezentrii binare a etichetei unui procesor) i continu de-a lungul dimensiunilor mai mici, n pai consecutivi.
Procedura execut p pai de comunicare, unul pe fiecare dimensiune a hipercubului. Comunicarea se desfoar de la dimensiunea cea mai mare spre cea mai mic (desi aceasta ordine nu conteaz). Indicele i al buclei indic dimensiunea curent pe care are loc comunicarea. Numai procesoarele ce au valoarea 0 pe cei mai puin semnificativi i bii particip la comunicarea pe dimensiunea i. De exemplu, pentru hipercubul tridimensional din figura anterioar i are valoarea 2 n prima faz. De aceea numai procesoarele 0 si 4 comunic, avnd cei mai puin semnificativi 2 biti 0. In faza urmtoare, cnd i = 1 toate procesoarele cu ultimul bit 0 (0, 2, 4, 6) particip la comunicare. Variabila mask este folosit pentru determinarea procesoarelor ce iau parte la comunicare ntr-o iteraie. Variabila mask are d(=log p) cifre binare care iniial sunt toate 1. La nceputul fiecarei iteraii, cel mai semnificativ bit diferit de 0 este resetat la 0. Apoi se determin procesoarele ce vor participa n acest pas la comunicare. De exemplu, pentru hipercubul din figura anterioar, mask are iniial valoarea 111 i va deveni 011 n timpul iteraiei corespunztoare lui i = 2. Dintre procesoarele alese pentru comunicare de-a lungul dimensiunii i, procesoarele cu bitul i =0 trimit mesajul, iar cele cu bitul i=1 primesc mesajul. Algoritmul se termin dup ce comunicarea s-a desfurat pe toate dimensiunile. proc ONE_TO_ALL_BC(d, my_id, M) begin mask =2d-1; /*Toate cele d cifre binare ale matii mask devin 1*/ for i = d-1 downto 0 do mask = mask XOR 2i; /* Bitul i din mask devine 0 */ if (my_id AND mask)=0 then /* Dac ultimele i cifre binare din my_id sunt 0 */ if (my_id AND 2i)=0 then msg_destination = my_id XOR 2i; send M to msg_destination; else msg_source = my_id XOR 2i; receive M from msg_source; end
18
Fiecare din cei logp pai de comunicare dureaz ts+twm pentru transferul unui singur mesaj pe fiecare dimensiune. Astfel, timpul total necesar pentru difuzia unu-la-toi pe un hipercub cu p procesoare este: Tone_to_all =( ts+twm ) log p Procedura de mai sus funcioneaz corect numai dac nodul 0 este sursa difuziei. Pentru o surs arbitrar trebuie ca procesoarele hipercubului s fie renumerotate cu valoarea rezultat din aplicarea operaiei XOR etichetei iniiale i celei a sursei. Procedura ce funcioneaz corect pentru orice surs din multimea {0, p-1} este urmtoarea: proc GENERAL_ONE_TO_ALL_BC(d, my_id, source, M) begin my_virtual_id = my_id XOR source; mask = 2d 1; for i = d - 1 downto 0 do /* Bucla externa*/ begin mask = mask XOR 2i; /* Seteaza bitul i al mastii in 0*/ if (my_virtual_id AND mask) = 0 then if (my_virtual_id AND 2i) = 0 then begin virtual_dest = my_virtual_id XOR 2i; send M to (virtual_dest XOR source); /* Converteste virtual_dest in eticheta destinatiei fizice*/ end else begin virtual_source = my_virtual_id XOR 2i; receive M from (virtual_source XOR source); /*Converteste virtual_source in eticheta sursei fizice */ end end end
19
Comunicarea personalizat unu-la-toi pe diferite arhitecturi este similara cu cea a difuziei unu-la-toi. Din aceste considerente va fi prezentat detaliat doar comunicarea personalizat unu-la-toi pe hipercub. Figura de mai jos descrie paii de comunicare pentru comunicarea personalizat unu-la-toi pe un hipercub cu 8 procesoare. Iniial, nodul surs (procesorul 0) conine mesajele destinate fiecrui nod al hipercublui. In figur mesajele sunt definite ca etichete ce indic nodul destinaie. In prima etap, sursa trimite jumtate din mulimea mesajelor de care dispune unuia dintre vecinii si. n pai consecutivi, fiecare procesor care dispune de mesaje, transfera jumtate din mulimea mesajelor de care dispune unui vecin care nu a primit nc nici un set de mesaje. Vor fi log p pai de comunicare, corespunztor celor logp dimensiuni ale hipercubului. Modul n care se desfoar comunicrile este identic cu cel de la difuzia toi-la-toti. Numai dimensiunea i coninutul mesajelor sunt diferite.
20
Toate muchiile unui hipercub cu p procesoare de-a lungul unei anumite dimensiuni leag dou subcuburi cu p/2 procesoare. Dup cum este ilustrat n figur, n fiecare pas de comunicare personalizat, mesajele trec de la un subcub la altul. Din mulimea mesajelor de care dispune un procesor nainte de nceperea comunicrii ce corespunde la o anumit dimensiune, jumatate trebuie trimise unui procesor din cellalt subcub. In fiecare pas, un procesor pstreaz jumtate din mulimea mesajelor de care dispune, mesaje necesare procesoarelor din propriul subcub i trimite cealalta jumtate a mulimii mesajelor ctre procesoare din cellalt subcub. Timpul n care datele sunt distribuite este: Tone_to_oll_pers = ts log p + tw m ( p/2+p/4+p/8++1) = = ts log p + tw m ( p-1)
21
Difuzarea toi-la-toi
Fiecare procesor trimite un mesaj Mp la toi ceilali. Operaia invers o reprezint acumularea multinod, n care fiecare procesor este destinaia unei acumulri ntr-un nod.
O modalitate de a executa o operaie de difuzare toi-la-toi este aceea de a executa p operaii de difuzare unu-la-toi. Canalele de comunicaii pot fi utilizate mai eficient prin concatenarea mesajelor transmise ntr-un pas de comunicare ntr-un singur mesaj.
22
proc ALL_TO_ALL_BC_MESH(my_id, my_msg, p, result) begin /*Comunicatie de-a lungul liniilor*/ left = (my_id - 1) mod p; right = (my_id + 1) mod p; result = my_msg; for i = 1 to p -1 do send msg to right; receive msg from left; result = result U msg; end; /* Comunicatie pe coloane*/ up= (my_id - p p) mod p; down= (my_id + p p) mod p; msg = result; for i = 1 to p -1 do send msg to down; receive msg from up; result = result msg; end; end.
24
In primul pas, fiecare procesor trimite toate mesajele sale ca un mesaj de dimensiune m(p-1) la unul dintre vecinii si (toate procesoarele trimit n aceeai direcie). Din cele m mesaje primite de un procesor n acest pas, un singur mesaj i este adresat. De aceea fiecare procesor extrage informaia ce i aparine i trimite mai departe restul mesajelor (p-2). Acest proces continu. Dimensiunea mesajului transmis scade cu valoarea m n fiecare pas. Intr-un pas, fiecare procesor adaug n lista mesajelor primite un mesaj diferit de cele din lista sa. Deci, n p-1 pai, fiecare procesor primete mesajele trimise de celelalte procesoare. Dimensiunea unui mesaj transmis n faza i pe un inel de procesoare fiind m(p-1), timpul total al acestei operaii este: Tall_to_all_pers = (ts + twmp)(p-1)
26
In procedura descris mai sus, toate mesajele au fost trimise n aceeai direcie. Dac jumtate din mulimea mesajelor este trimis ntro direcie i cealalt jumtate este expediat n direcia opus, valoarea lui tw poate fi redus la jumtate.
La sfritul celei de-a doua faze, procesorul i are mesajele ({0,i},...,{8,i}), unde 0i 8. Grupurile de procesoare ce comunic n fiecare faz sunt ncercuite cu o linie punctat. 27
Inainte de a doua faz a comunicrii, mesajele n fiecare procesor sunt sortate din nou, n funcie de linia procesorului destinaie de aceast dat. Apoi comunicarea se desfoar similar primei faze, n coloanele plasei. La sfritul acestei faze fiecare procesor a primit un mesaj de la toate celelalte procesoare. Timpul total al comunicrii personalizate toila-toi cu mesaje de dimensiune m pe o retea bidemensional cu p procesoare este: Tall_to_all_pers = (2ts + twmp)( p -1) Expresia de mai sus nu ia n considerare timpul necesar rearanjrii datelor.
28
Sortarea paralel
Exist o vast literatur avnd ca subiect probleme de sortare. Aceasta se explic prin faptul c sortarea apare ca substask n soluiile algoritmice a foarte multe probleme. Problema poate fi enunat astfel: Date fiind n numere a1,...,an, se dorete renumerotarea lor astfel nct aiaj, i,j{1,...,n}: i<j. Se presupune, pentru simplitate, c aiaj daca ij.
29
Explicare algoritm
Procedura are trei faze: Faza 1: determinarea rangurilor relative R[i+n-1,j]; sunt necesare n2 procesoare CREW-PRAM i O(1) uniti de timp sau n2/logn procesoare si O(logn) uniti de timp. Faza 2: calcularea poziiilor P[j]; timpul de execuie este de O(logn) cu nn/logn procesoare CREW-PRAM . Faza 3: permutarea (plasarea) elementelor aj pe poziiile corecte; n procesoare, O(1) timp.
Complexitatea timp
Algoritmul necesit O(logn) timp si O(n2/logn) procesoare CREWPRAM . Citirile simultane din prima faz pot fi evitate fr a afecta complexitatea.
30
31
Complexitatea
O(n) timp cu n processoare CREW-PRAM. Algoritmul este optimal pentru arhitectur. Fiecare procesor este solicitat O(n) timp. Totui, costul nu este optimal: nr.procesoaretimpul paralel = n2, iar timpul pentru cel mai rapid algoritm secvential = O(nlogn).
1. 2. 3. 4.
32
33
Reea de comparatori (R2) care transform o secven oarecare ntr-o secven biton
Concluzii
Cele dou reele combinate n ordinea (R2,R1) transform o secven oarecare ntr-o secven sortat.
2. d este un parametru binar specificnd ordinea cresctoare (d = 0) sau descresctoare (d = 1) a cheilor de sortare; 3. COMP_EX(x,y;d) desemneaz operaia care aranjeaz dou numere x i y n ordine cresctoare (d=0) sau descresctoare(d=1).
35
36
37
Calcul matricial
Transpunerea unei matrice
Formularea problemei: Dat fiind matricea Ann se cere s se calculeze matricea AT[i,j] = A[j,i] pentru toi i,j=1,2,,n Obs. Nu se efectuez calcule, ci doar micari de elemente. Timpul secvenial Ts(n) = (n2). Algoritmul implementat pe o maina EREW PRAM este banal i are cost (1).
Paii de comunicare
Configuraia final
38
Paii de comunicare
Rearanjamente locale
Divizarea fiecrui bloc n sub-blocuri n fiecare bloc: Intreschimbarea sub-blocurilor stnga-jos cu dreapta-sus
39
Ultima divizare
Configuraia final
Complexitatea
Recusia se oprete cnd dimensiunea blocului este n2/p. Numrul de pai = log4p=(log2p)/2 . Fiecare pas de comunicare se realizeaz pe dou din dimensiunile hipercubului (2 muchii). Transpunerea local este (n2/p). Rezult urmtoarele: - Timpul total este ((n2/p)logp) - Costul = (n2logp) nu este optimal.
Implementare standard
Arhitectura naturala este arhitectura cu topologie de tip plas. Numrul de procesoare p=q2. Fiecare procesor Pi,j dispune n memoria local de blocul Ai,j i un bloc Bi,j i calculeaza Ci,j. Pentru a calcula Ci,j procesorul Pi,j are nevoie de Ai,k si Bk,j k=0,,q-1. Pentru fiecare k, blocurile Ai,k i Bk,j sunt obinute printr-o difuzie toi-la-toi pe linii i apoi pe coloane.
41
42
43
Sisteme de ecuatii
Sistem standard de ecuatii liniare
44
Versiunea paralel
proc GAUSSIAN ELIMINATION (A, b, y) begin for k = 0 to n-1 do for j = k+1 to n-1 do A[k,j] = A[k,j] / A[k,k]; /* pasul de divizare */ y[k] = b[k] / A[k,k]; A[k,k] = 1; for i = k+1 to n-1 par do for j = k +1 to n-1 do /* pasul de eliminare */ A[i,j] = A[i,j] - A[i,k] A[k,j]; b[I] = b[i] - A[i,k]y[k]; A[i,k] = 0; end
45
Implementarea pasului de eliminare for j = k +1 to n-1 do A[i,j] = A[i,j] - A[i,k] A[k,j]; b[i] = b[i] - A[i,k]y[k]; A[i,k] = 0;
46
Complexitatea
Operaia min{C[i,j],mink{w[i,k,j]; k = 1,...,n}} poate fi implementat prin procedura sum, ,, fiind n acest caz operatorul de minimizare. Daca se utilizeaz modelul CREW-PRAM, complexitatea timp a acestei operaii este O(logn) iar numrul de procesoare utilizate este n/logn. 47
Rezult pentru algoritm un timp de execuie T(n)=O(log2n) si un necesar de procesoare de O(n3/logn). Pe o main CRCW-PRAM, operaia de minimizare anterior menionat se poate efectua n timp constant cu n2 procesoare. Consecina este reducerea timpului de execuie la O(logn), rezultnd ns o cretere a numrului procesoarelor la O(n4).
k 1
Implementari sistolice
Abordrile sistolice ale problemei algebrice a drumurilor sunt foarte numeroase. ntre acestea, implementarea algoritmului lui Kleen pe o retea de tip plas, dat de Y. Robert si D. Trystram, 1986, este remarcabil. Parametrii algoritmului sunt T = 5n-2 si A O(n2).
48
49
Algoritmul implementat de reeaua de mai sus este derivat din algoritmul Warshal-Floyd astfel: proc drumuri_maxime(C,n); begin for k = 1 to n-1 do for j = 1 to n do for i = k+1 to n do C[i,j] = max{C[i,j],C[i,k]+C[k,j]} end; Procedura drumuri_maxime transform elementele matricei C dup formula: Ck[i,j] = max{Ck-1[i,j],Ck-1[i,k]+Ck-1[k,j]} unde Ck-1 respectiv Ck definesc starea matricei C nainte i dup iteraia k. Elementele Cn-1[i,j] vor conine costurile drumurilor maxime de la i la j. n final doar elementele liniei n a matricei C vor ajunge n starea n-1, deci vor conine valori corespunzatoare drumurilor maxime.
50
51
52
Comentarii bibliografice
Seciunea Primitive ale calculului paralel are la baz cartea, Efficient Parallel Algorithms, Cambridge University Press, 1988, autori A. Gibbons, W. Rytter [1]. Seciunile Comunicri colective, Calcul Matricial i Sisteme de ecuaii sunt inspirate din cartea Introduction to Parallel Computing: Design and Analysis of Algorithms, Benjamin-Cummings,1994, autori V. Kumar, A. Grama A. Gupta i G Karypis [2]. Seciunile Sortarea paralel i Drumuri optime n grafuri sunt sinteze realizate pe baza lucrrilor [1]-[5]. Unele figuri i/sau exemple sunt preluate din crile [1], [2], [5].
53
54
55
56
Standardul MPI
MPI (Message Passing Interface) reprezint o bibliotec pentru programarea paralel. MPI a fost creat n cadrul grupului MPI Forum ntre 1993 i 1994, cnd a fost elaborat i standardul 1.2. Acest standard definete mai mult de 120 de funcii care ofer suport pentru: Comunicri point-to-point: Operaiile de baz n recepia i trimiterea mesajelor de ctre procese cu MPI sunt send i receive cu variantele de implementare a acestora. Operaii colective: Sunt operaii definite pentru comunicri care implic un grup de procese. Grupuri de procese i contexte de comunicare Topologia proceselor: n MPI un grup de procese este o colecie de n procese n care fiecare proces are atribuit un rang ntre 0 i n-1. n multe aplicaii paralele o atribuire liniar a rangurilor nu reflect logica comunicrii ntre procese. Adesea procesele sunt aranjate n modele topologice ca de exemplu o plas cu 2 sau 3 dimensiuni. ntr-un caz i mai general procesele pot fi descrise de un graf i vom face referire la aranjarea acestor procese ntr-o topologie virtual. O distincie clar trebuie fcut ntre o topologie virtual i topologia de baz a suportului, hardware-ul propriu-zis. Topologia virtual poate fi exploatat de sistem n atribuirea proceselor ctre procesoarele fizice, dac acest lucru ajut la creterea performanelor de comunicare pentru o anumit main, dar realizarea acestui lucru nu face obiectul MPI. Descrierea unei topologii virtuale depinde numai de aplicaie i este independent de sistemul de calcul. Suport pentru limbajele Fortran77 i C: Aplicaiile paralele folosind MPI pot fi scrise in limbaje ca Fortran77 i C. Obinerea informaiilor despre mediu i managementul acestora: Sunt introduse rutine pentru obinerea i setarea unor parametri pentru execuia programelor corespunztoare diferitelor implementri ale MPI. Standardul MPI a ajuns la versiunea 2.0 i ofer n plus suport pentru: Extinderea operaiilor colective: n MPI-1 intercomunicarea reprezenta comunicarea ntre dou grupuri de procese disjuncte. n multe funcii argumentul de comunicare poate fi unul de intercomunicare, excepie facnd operaiile colective. Acest lucru este implementat n MPI-2 care extinde utilizarea funciilor pentru operaiile colective. 57
Crearea proceselor i managementul dinamic al acestora: Obiectivele principale sunt oferirea posibilitaii de a crea aplicaii cu un numar variat de task-uri (task farm) i cuplarea aplicaiilor dup modelul client/server. One-sided Communication: Ideea de baz este aceea ca un task s aib acces direct la memoria altui task (asemantor modelului shared memory), ca taskul proprietar al unei zone de memorie s permit accesul i oferirea unor metode de sincronizare. Parallel File I/O: Mecanismele care ofer operaii de I/O paralele, permit taskurilor s acceseze fiierele ntr-un mod controlat dar sunt dependente de arhitectur. MPI-2 definete rutine de nivel nalt pentru a furniza un model de programare unic pentru operaiile de I/O paralele. Suport pentru alte limbaje de programare: Este oferit suportul pentru C++. De asemenea exist i o variant pentru Java i o variant orientat obiect (OOMPI).
Modelul de execuie
Execuia unui program scris cu apeluri MPI const n rularea simultan a mai multor procese, definite n mod static (MPI-1) sau dinamic (MPI-2), identice sau diferite i care comunic colectiv sau de la unul la altul (point-to-point). Procesele comunicante fac parte din acelai comunicator, care definete domeniul de comunicare. n cadrul domeniului de comunicare fiecare proces are un rang. De fapt comunicatorul asigur un mecanism de identificare a grupurilor de procese i de protecie a comunicrii. Un comunicator poate fi de dou feluri: intracomunicator pentru procese din interiorul aceluiai grup i intercomunicator pentru comunicrile dintre grupuri. Exist ase apeluri de funcii mai importante care ar putea fi suficiente pentru scrierea unui program MPI:
MPI_Init MPI_Finalize MPI_Comm_size MPI_Comm_rank MPI_Send MPI_Receive
iniiaz un calcul MPI; termin un calcul MPI; determin numrul proceselor; determin identificatorul procesului; trimite un mesaj; primete un mesaj.
58
Observaie: Dup apelul MPI_Finalize nu se mai poate apela nici o funcie MPI. Exemplu:
main(int argc, char **argv) { MPI_Init (&argc, &argv); MPI_Comm_size (MPI_COMM_WORLD, count); MPI_Comm_rank (MPI_COMM_WORLD, &myrank); printf (Eu sunt %d din %d, myrank, count); MPI_Finalize(); }
Parametrul MPI_COMM_WORLD precizeaz c toate procesele aparin aceluiai grup i reprezint comunicatorul iniial care este valabil pentru toate procesele aceluiai grup. Procesele pot executa instruciuni diferite, dar pentru aceasta trebuie s se asigure un mecanism de difereniere: Exemplu :
main(int argc, char **argv) { MPI_Init (&argc, &argv); ............ MPI_Comm_size (MPI_COMM_WORLD, count); MPI_Comm_rank (MPI_COMM_WORLD, &myrank); if (myrank == 0) master(); else slave(); ............ MPI_Finalize(); }
unsigned char unsigned short int unsigned int float double long double
Tipurile de date MPI_BYTE i MPI_PACKED nu corespund nici unui tip de date din C.
60
[OUT buf] [IN count] [IN datatype] [IN source] [IN tag] [IN comm] [ OUT status]
- adresa zonei de memorie n care este recepionat mesajul n cazul n care este primit corespunztor parametrilor funciei - numrul de elemente care pot fi primite n zona tampon - tipul datelor pentru fiecare element din buffer primit - identificatorul procesului surs - identificator de mesaj - comunicatorul - variabil util pentru aflarea dimensiunii, identificatorului de mesaj i a sursei, codurilor de eroare
Exemplu:
#include "mpi.h" main( int argc, char **argv ) { char message[20]; int myrank; MPI_Status status; MPI_Init( &argc, &argv ); MPI_Comm_rank( MPI_COMM_WORLD, &myrank ); if (myrank == 0) /* codul pentru procesul zero */ { strcpy(message,"Hello"); MPI_Send(message, strlen(message), MPI_CHAR, 1, 99, MPI_COMM_WORLD); } else /* codul pentru procesul 1 */ { MPI_Recv(message, 20, MPI_CHAR, 0, 99,MPI_COMM_WORLD, &status); printf("received :%s:\n", message); } MPI_Finalize(); }
O operaie de tip send n mod buffered poate fi iniiat indiferent dac operaia receive corespunztoare a fost iniiat sau nu. n acest mod terminarea nu depinde de apariia unei operaii receive. Pentru a termina operaia poate fi necesar ca mesajul s fie ntr-un buffer local. n acest scop, bufferul trebuie furnizat de ctre aplicaie. Spaiul (de memorie) ocupat de buffer este eliberat atunci cnd mesajul este transferat ctre destinaie sau cnd operaia este anulat. O operaie de tip send n mod sincron poate fi iniiat chiar dac o operaie receive corespunztoare a fost sau nu iniiat. Oricum, operaia va fi ncheiat cu succes numai dac o operaie receive corespunztoare a fost iniiat i a nceput recepia mesajului trimis prin send. Astfel, ncheierea operaiei send sincrone nu indic numai c bufferul poate fi reutilizat, dar de asemenea indic i faptul c receptorul a ajuns ntr-un anumit punct din execuia sa i anume faptul c a iniiat o operaie receive corespunztoare. Observaie: Modul sincron asigur faptul c o comunicare nu se termin la fiecare sfrit de proces nainte ca ambele procese s ajung la aceeai operaie de comunicare. n modul sincron operaia de comunicare este ncheiat simultan de ctre ambele funcii indiferent care este primul apel. O operaie de tip send n mod ready poate fi iniiat numai dac o operaie receive a fost deja iniiat, altfel operaia este o surs de erori. Pe unele sisteme acest lucru poate permite nlturarea operaiilor de tip hand-shake i au ca rezultat creterea performanelor. De aceea, ntr-un program scris corect o operaie ready send poate fi nlocuit de una standard fr a avea alt efect asupra comportrii programului dect performana. Apelul funciei Bsend:
MPI_Bsend (buf, count, datatype, dest, tag, comm) [ [ [ [ [ [ IN IN IN IN IN IN buf] count] datatype] dest] tag] comm] -adresa zonei tampon (bufferului) pentru datele de trimis - numrul de elemente de trimis din buffer - tipul datelor pentru fiecare element din buffer - rangul destinaiei - identificator de mesaj - comunicatorul
62
Observaie: n modul de lucru cu zon tampon (buffered), trebuie s se asigure spaiul tampon, prin apelul MPI_buffer_attach(), dup care este eliberat cu MPI_buffer_detach(). Apelul funciei Ssend:
MPI_Ssend (buf, count, datatype, dest, tag, comm) [ [ [ [ [ [ IN IN IN IN IN IN buf] count] datatype] dest] tag] comm] - adresa zonei tampon (bufferului) pentru datele de trimis - numrul de elemente de trimis din buffer - tipul datelor pentru fiecare element din buffer - rangul destinaiei - identificator de mesaj - comunicatorul
Observaie: n modul sincron, indiferent care este primul apel, operaia se ncheie simultan de ctre ambele funcii. Apelul funciei Rsend:
MPI_Rsend (buf, count, datatype, dest, tag, comm) [ [ [ [ [ [ IN IN IN IN IN IN buf] count] datatype] dest] tag] comm] - adresa zonei tampon (bufferului) pentru datele de trimis - numrul de elemente de trimis din buffer - tipul datelor pentru fiecare element din buffer - rangul destinaiei - identificator de mesaj - comunicatorul
Observaie: n modul ready apelul receive trebuie s-l precead obligatoriu pe cel pentru operaia send.
63
64
int MPI_Irecv(void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Request *request)
Din apelul de recepie se revine chiar dac nu exist nici un mesaj primit. Acest mod de lucru poate conduce la erori i n acest caz se folosesc funcii de detectare a terminrii operaiei: MPI_Wait() i MPI_Test(). Exemplu:
MPI_Comm_Rank (MPI_COMM_WORLD,&myrank); if (myrank == 0) { int a; MPI_Isend(&a,1,MPI_INT,msgtag,MPI_COMM_WORLD,req1); calcul(); MPI_Wait(req1, status); } else if (myrank == 1) { int b; MPI_Recv(&b,1,MPI_INT,0,mesgtag,MPI_COMM_WORLD ,status); }
Funcia MPI_Wait() se termin dup terminarea operaiei, iar MPI_Test() se termin imediat i returneaz un indicator a crui stare arat dac operaia s-a ncheiat sau nu. n afar de aceste funcii mai exist MPI_Waitany(), MPI_Testany(),MPI_Waitall(),MPI_Testall(), MPI_Waitsome(), MPI_Testsome(). Pentru mai multe detalii despre aceste tipuri de funcii consultai helpul distribuiei MPI. Observaie: 1. Funciile de comunicare fr blocare ofer posibilitatea suprapunerii n timp a operaiilor de comunicare cu cele de calcul, ceea ce poate reduce timpul de execuie al aplicaiei. 2. Funcia receive poate fi apelat numai n modul standard. Exemplu:
MPI_Comm_Rank (MPI_COMM_WORLD,&myrank); if (myrank == 0) { int a; MPI_Isend(&a,1,MPI_INT,msgtag,MPI_COMM_WORLD,req1); calcul();
65
Exemplu: Model de aplicaie care implementeaz problema productoriconsumatori (cazul n:1 ) folosind comunicri neblocante [1].
... typedef struct{ char data[MAXSIZE]; int datasize; MPI_Request req; }Buffer; Buffer *buffer; MPI_Status status; ... MPI_Comm_rank(comm, &rank); MPI_comm_size(comm, &size); if (rank!=size-1) { //codul pentru producator //initializare producatorul aloca un buffer buffer= (Buffer *)malloc(sizeof(Buffer)); while(1) { //bucla principala //producatorul umple dufferul cu date si intoarce //numarul de bytes din buffer produce(buffer->data, &buffer->datasize); //trimite datele MPI_Send(buffer->data, buffer->datasize, MPI_CHAR, size-1, tag, comm); } //end while }//end if else{ // rank==size-1; codul pentru consumator //initializare consumatorul aloca un buffer pentru //fiecare producator buffer=(Buffer )malloc(sizeof(Buffer)*(size-1)); // se apleleaza un receive pentru fiecare producator 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<size-1; i++){ //bucla principala MPI_Wait(&(buffer[i].req), &status); // gaseste numarul de bytes primiti MPI_Get_count(&status, MPI_CHAR, &(buffer[i].datasize)); // consumatorul elibereaza bufferul de date consume(buffer[i].data, buffer[i].datsize);
66
//un nou receive MPI_Irecv(buffer[i].data, MAXSIZE, MPI_CHAR, i, tag, comm, &(buffer[i].req)); }//end for }//end else
Exemplu: Model de aplicaie care implementeaz problema productoriconsumatori (cazul n:1 ) folosind funcia de comunicare neblocante i funcia MPI_Test()[1] .
... typedef struct{ char data[MAXSIZE]; int datasize; MPI_Request req; }Buffer; Buffer *buffer; MPI_Status status; ... MPI_Comm_rank(comm, &rank); MPI_comm_size(comm, &size); if (rank!=size-1) { //codul pentru producator //initializare producatorul aloca un buffer buffer= (Buffer *)malloc(sizeof(Buffer)); while(1) { //bucla principala //producatorul umple dufferul cu date si intoarce //numarul de bytes din buffer produce(buffer->data, &buffer->datasize); //trimite datele MPI_Send(buffer->data, buffer->datasize, MPI_CHAR, size-1, tag, comm); } //end while }//end if else{ // rank==size-1; codul pentru consumator //initializare consumatorul aloca un buffer pentru //fiecare producator buffer=(Buffer )malloc(sizeof(Buffer)*(size-1)); // se apleleaza un receive pentru fiecare producator for(i=0; i<size-1; i++) MPI_Irecv(buffer[i].data, MAXSIZE, MPI_CHAR, i, tag, comm, &(buffer[i].req)); i=0; while(1){ //bucla principala for(flag=0;!flag; i=(i+1)%(size-1)){ MPI_Test(&(buffer[i].req),&flag,&status); }//end for MPI_Get_count(&status, MPI_CHAR, &(buffer[i].datasize));
67
// consumatorul elibereaza bufferul de date consume(buffer[i].data, buffer[i].datsize); //un nou receive MPI_Irecv(buffer[i].data, MAXSIZE, MPI_CHAR, i, tag, comm, &(buffer[i].req)); }//end while }//end else
68
[ [ [ [ [ [ [
- adresa zonei tampon (bufferului) pentru datele de trimis - numrul de elemente de trimis din buffer - tipul datelor pentru fiecare element din buffer - rangul destinaiei - identificator de mesaj - comunicatorul - communication request
Observaii: Sistemele de comunicare cu transfer de mesaje sunt n general nedeterministe, adic nu garanteaz c ordinea n care se transmit mesajele este aceeai cu ordinea n care mesajele sunt recepionate, programatorului revenindu-i sarcina gsirii mecanismelor necesare pentru obinerea unui calcul determinist.
Comunicri colective
Conceptul cheie al comunicrilor colective este acela de a avea un grup de procese participante. Pentru aceasta biblioteca MPI ofer urmtoarele funcii: bariera - sincronizarea prin barier pentru toi membrii grupului (MPI_Barrier); broadcast - transmiterea de la un membru al grupului ctre ceilali membri (MPI_Bcast); gather - adunarea datelor de la toi membrii grupului la un singur membru din grup (MPI_Gather); scatter - mprtierea datelor de la un membru la toi membrii grupului (MPI_Scatter); all gather - o variaie a MPI_Gather n care toi membrii grupului primesc rezultatele, nu numai procesul root (MPI_Allgather); all-to-all scatter/gather - adunarea/mpratierea datelor de la toi membrii grupului ctre toi membrii (MPI_Alltoall o extensie a MPI_Allgather n care fiecare proces trimite date distincte ctre fiecare proces din grup); operaii de reducere adunare, nmulire, min, max, sau funcii definite de utilizator n care rezultatele sunt trimise la toi membrii grupului sau numai la un membru. 69
Observaii: - Toate funciile sunt executate colectiv (sunt apelate de toate procesele dintr-un comunicator); toate procesele folosesc aceeai parametri n apeluri. - Funiile nu au un identificator de grup ca argument explicit. n schimb avem un comunicator ca argument. Un inter-comunicator nu este permis ca argument n funciile ce realizeaz operaiile colective dect n implementrile standardului MPI 2.0. Caracteristicile comunicrilor colective: - Comunicrile colective i cele de tip unu-la-unu nu se vor influena reciproc. - Toate procesele trebuie s execute comunicri colective. - Sincronizarea nu este garantat, excepie realiznd doar bariera. - Nu exist comunicri colective neblocante. - Nu exist tag-uri. - Bufferele de recepie trebuie s fie exact de dimensiunea potrivit .
Bariera
Bariera este un mecanism de sincronizare a unui grup de procese. Dac un proces a fcut apelul MPI_Barrier(com), unde com este comunicatorul, atunci el va atepta pn cnd toate procesele au efectuat acest apel. Apelul funciei Barrier:
MPI_Barrier( comm ) [IN comm] - comunicator
Difuzia (Broadcast)
Funcia MPI_Bcast trimite un mesaj de la procesul cu rangul root ctre toate procesele din grup, inclusiv procesului root.
70
P0 P1 P2 P3 P4 P5
A0
broadcast
A0 P0 A0 P1 A0 P2 A0 P3 A0 P4 A0 P5
Funcia este apelat de toi membrii grupului, folosind acelai argument pentru comunicator, root. La ieirea din funcie, coninutul buffer-ului de comunicare al procesului root a fost copiat de toate procesele. Apelul funciei Bcast:
MPI_Bcast( buffer, count, datatype, root, comm ) [ [ [ [ [ INOUT buffer] IN count] IN datatype] IN root] IN comm] - adresa de nceput a buffer-ului pentru date - numrul de intrri din buffer - tipul datelor din buffer - rangul procesului root care iniiaz comunicarea - comunicator
Exemplu 8:
#include<mpi.h> void main (int argc, char *argv[]) { int rank; double param; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD,&rank); if(rank==5) param=23.0; MPI_Bcast(¶m,1,MPI_DOUBLE,5,MPI_COMM_WORLD); printf("P:%d dupa broadcast parametrul este %f\n", rank, param); MPI_Finalize(); }
71
A0 A1 A2 A3 A4 A5
scatter
A0 P0 A1 P1 A2 P2 A3 P3 A4 P4 A5 P5
Rezultatul este acelai ca i cum procesul root ar executa n operaii de tip send MPI_Send(sendbuf+i* sendcount* extent(sendtype), sendcount, sendtype, i,...), i fiecare proces execut o operaie receive MPI_Recv(recvbuf, recvcount, recvtype, i,...). Apelul funciei Scatter:
MPI_Scatter (sendbuf, sendcount, sendtype, recvbuf, recvcount, recvtype, root, comm) [IN sendbuf] [IN sendcount] [IN sendtype] [OUT recvbuf] [IN recvcount] [IN recvtype] [IN root] [IN comm] - adresa de nceput a zonei buffer-ului pentru datele de trimis - numrul de elemente din buffer - tipul datelor din buffer - adresa buffer-ului de recepie - numrul de elemente pentru fiecare recepie n parte - tipul datelor din buffer-ul de recepie - rangul proceselor care trimite datele - comunicator
Observaii: - ntr-o alt interpretare, funcia Scatter ar nsemna: procesul root trimite un mesaj cu MPI_Send(sendbuf, sendcount-n, 72
segmentul i fiind transmis la procesul i din grup i fiecare proces primete mesajul dup descrierea anterioar. - Toate argumentele funciei sunt semnificative numai n procesul root, n timp ce pentru celelalte procese numai argumentele recvbuf, recvcount, recvtype, root i comm sunt semnificative. - Sendbuffer este ignorat n procesele care nu au rangul root.
100 100 100 Toate procesele
100
100
sendbuf
Exemplu: Transmiterea a 100 de ntregi de ctre procesul root ctre fiecare proces din grup (inversul exemplului 8).
MPI_Comm comm; int gsize,*sendbuf; int root, rbuf[100]; ... MPI_Comm_size( comm, &gsize); sendbuf = (int *)malloc(gsize*100*sizeof(int)); ... MPI_Scatter( sendbuf, 100, MPI_INT, rbuf, 100, MPI_INT, root, comm);
73
P0
A0 A1 A2 A3 A4 A5
gather
A0 P0 A1 P1 A2 P2 A3 P3 A4 P4 A5 P5
Rezultatul este acelai ca i cum fiecare proces (inclusiv procesul root) ar executa un apel al funciei MPI_Send(sendbuf, sendcount, sendtype, root, ...) i procesul root ar executa n apeluri MPI_Recv(recvbuf+i*recvcount*extent(recvtype), recvcount, recvtype, i ,...) unde extent(recvtype) este tipul obinut din apelul MPI_Type_extent( ) vezi help-ul. Apelul funciei Gather:
MPI_Gather( sendbuf,sendcount,sendtype,recvbuf, recvcount, recvtype, root, comm) [ [ [ [ IN sendbuf] IN sendcount] IN sendtype] OUT recvbuf] adresa de nceput a zonei buffer-ului pentru date numrul de elemente din buffer tipul datelor din buffer adresa buffer-ului de recepie (are semnificaie numai la procesul root) numrul de elemente pentru fiecare recepie n parte (are semnificaie numai la procesul root) tipul datelor din buffer-ul de recepie (are semnificaie numai la procesul root) rangul proceselor care recepioneaz datele comunicator
74
Observaii: - ntr-o alt intrepretare, funcia Gather ar nsemna: n mesaje trimise de procesele din grup sunt concatenate n ordinea rangului proceselor i mesajul rezultat este primit de procesul root. - Buffer-ul de recepie este ignorat pentru procesele care nu au rangul root. - Argumentul recvcount la procesul root indic dimensiunea datelor primite de la fiecare proces i nu numrul total de date primite. Exemplu: Recepia a 100 ntregi de la fiecare proces din grup
MPI_Comm comm; int gsize,sendarray[100]; int root, *rbuf; ... MPI_Comm_size( comm, &gsize); rbuf = (int *)malloc(gsize*100*sizeof(int)); MPI_Gather( sendarray, 100, MPI_INT, rbuf,100, MPI_INT, root, comm);
100 100 100 Toate procesele
100
100
recvbuf
Exemplu: Exemplul de mai sus modificat numai procesul root aloc memorie pentru buffer-ul de recepie.
MPI_Comm comm; int gsize,sendarray[100]; int root, myrank, *rbuf;
75
... MPI_Comm_rank( comm, myrank); if ( myrank == root) { MPI_Comm_size( comm, &gsize); rbuf = (int *)malloc(gsize*100*sizeof(int)); } MPI_Gather( sendarray, 100, MPI_INT, rbuf, 100, MPI_INT, root, comm);
Operaii de reducere
Funciile pe care le vom prezenta n continuare realizeaz operaii de reducere peste toi membrii unui grup. Operaiile de reducere implic date distribuite peste un grup de procese. Funciile de reducere globale sunt urmtoarele: o operaie de reducere care ntoarce rezultatul la un singur nod, o operaie care ntoarce rezultatul la toate nodurile i o operaie de scanare (prefixul paralel). n plus exist o operaie de tipul reduce-scatter care combin funcionalitatea operaiei de reducere i a operaiei scatter. Operaiile care se realizeaz asupra datelor pot fi predefinite sau definite de utilizator. Funcia MPI_Reduce combin elementele din buffer-ul de intrare al fiecrui proces din grup, folosind operaia op, i ntoarce rezultatul n buffer-ul de ieire al procesului root. P0 data buff reduce(); P1 data Pn-1 data
+ reduce(); reduce();
P0 P1 P2
A0
A1 A2
B0
B1 B2
C0
C1 C2
reduce
A0+A1+A2
B0+B1+B2
C0+C1+C2
P0 P1 P2
76
Standardul MPI include variante pentru fiecare operaie de reducere n care rezultatul este returnat tuturor proceselor din grup ca n figura de mai jos ( MPI_Allreduce). De asemenea trebuie ca toate procesele care particip la aceste operaii s primeasc acelai rezultat.
P0 A0 A1 A2 B0 B1 B2 C0 C1 C2
allreduce
P0
P1 P2
P1 P2
77
int MPI_Allreduce (void* sendbuf, void* recvbuf, int count, MPI_Datatype datatype, MPI_Op op, MPI_Comm comm)
Reduce-scatter este varianta n care rezultatul este transmis folosind scatter ctre toate procesele din grup.
P0 A0 A1 A2 B0 B1 B2 C0 C1 C2
reduce-scatter
P0
P1 P2
P1 P2
Operaii de reducere predefinite Urmtoarele operaii sunt predefinite pentru operaia de reducere: MPI_MAX MPI_MIN MPI_SUM MPI_PROD MPI_LAND MPI_BAND 78 maximum minimum sum produs i logic i pe bii
sau logic sau pe bii xor logic xor pe bii max value and location min value and location
1. funcia definit de utilizator 2. daca este comutativ true, altfel false 3. operaia
Operaiile definite de utilizator se presupun a fi asociative. Dac commute = true, atunci operaia trebuie s fie i comutativ i asociativ. Dac commute = false, atunci ordinea operanzilor este fix i este definit s fie ascendent, n ordinea rangului proceselor, pornind cu procesul zero. Ordinea evalurii poate fi schimbat, avnd avantajul asociativitii operaiei. Dac commute =true atunci ordinea evalurii poate fi schimbat, avnd avantajele comutativitii i asociativitii. function este funcia definit de utilizator i trebuie s aib urmtorul prototip:
void MPI_User_function ( void *invec, void *inoutvec, int *len, MPI_Datatype *datatype);
79
User_reduce( tempbuf, sendbuf, count, datatype) } if (rank < groupsize-1) { MPI_Send( sendbuf, count, datatype, rank+1, ...) } /*avem raspunsul n procesele groupsize-1 i le trimitem la root */ if (rank == groupsize-1) { MPI_Send( sendbuf, count, datatype, root, ...) } if (rank == root) { MPI_Recv(recvbuf, count, datatype, groupsize-1,...) }
Operaia de reducere ncepe, secvenial, de la procesul 0 la procesul group_size-1. Aceast ordine este aleas pentru a se respecta ordinea unui operator ne-comutativ definit de funcia User_reduce(). O implementare mai eficient este obinut folosind avantajul asociativitii. Comutativitatea poate fi folosit pentru a avantaja cazurile n care argumentul commute al MPI_Op_create este true. Operaiile de reducere predefinite pot fi implementate ca o bibliotec de operaii definite de utilizator. Cnd nu mai este nevoie de operaia definit de utilizator trebuie eliberat memoria. Acest lucru se face folosind funcia MPI_Op_free ce se apeleaz astfel:
MPI_Op_free( op)
[ IN op]
operaia
80
Complex c; for (i=0; i< *len; ++i) { c.real = inout->real*in->real inout->imag*in->imag; c.imag = inout->real*in->imag + inout->imag*in->real; *inout = c; in++; inout++; }
} ... /* fiecare proces are un ir de 100 numere complexe */ Complex a[100], answer[100]; MPI_Op myOp; MPI_Datatype ctype; /* se spune MPI cum este definit tipul Complex */ MPI_Type_contiguous( 2, MPI_DOUBLE, &ctype ); MPI_Type_commit( &ctype ); /*se creaz operaia definit de utilizator pentru nmulire*/ MPI_Op_create( myProd, True, &myOp ); MPI_Reduce( a, answer, 100, ctype, myOp,root,comm ); /*n acest punct, raspunsul, care const n 100 numere complexe este n procesul root*/
Calculul prefixelor
Pentru calculul prefixului paralel ca operaie de reducere a datelor distribuite peste un grup de procese, standardul MPI introduce funcia MPI_Scan. Operaia ntoarce n buffer-ul recvbuf a procesului cu rangul i, reducerea valorilor din buffer-ul sendbuf a proceselor cu rangurile 0, , i (inclusiv vezi figura). Tipul operaiilor suportate, precum i limitrile legate de sendbuf i recvbuf sunt cele ale MPI_Reduce.
P0 A0 A1 A2 B0 B1 B2 C0 C1 C2
scan
A0 A0+A1 A0+A1+A2
B0 B0+B1 B0+B1+B2
C0 C0+C1 C0+C1+C2
P0
P1 P2
P1 P2
81
Operaia a fost definit ca fiind inclusive scan adic calculul prefixului se face inclusiv pentru procesul cu rangul i. O alternativ ar fi definirea operaiei ntr-o manier exclusiv, cnd rezultatul pentru rangul i va include datele pn la rangul i-1. Ultima variant pare a fi mai avantajoas deoarece o operaie de tipul inclusive scan poate fi realizat dintr-o operaie de tip exclusive scan fr comunicare suplimentar. Datorit apariiei unor complicaii (scrierea operaiilor definite de utilizator) la scrierea operaiei de tip inclusive scan ca operaie de tip exclusive scan, acestea fiind lsate n grija programatorului, s-a renunat la varianta exclusive scan.
count, type, 0, comm); count, type, 1, comm); count, type, 1, comm); count, type, 0, comm);
82
Observaie: Se presupune c grupul de comunicare este {0,1}. Dou procese execut broadcast n ordine invers. Dac operaiile sunt sincronizate apare blocaj. Operaiile colective trebuie executate n aceeai ordine de toi membrii grupului de comunicare. Exemplu:Utilizare greit:
switch(rank) { case 0: MPI_Bcast(buf1, MPI_Bcast(buf2, break; case 1: MPI_Bcast(buf1, MPI_Bcast(buf2, break; case 2: MPI_Bcast(buf1, MPI_Bcast(buf2, break; }
count, type, 0, comm0); count, type, 2, comm2); count, type, 1, comm1); count, type, 0, comm0); count, type, 2, comm2); count, type, 1, comm1);
Observaii: - Presupunem c grupul de comunicare comm0 este {0,1}, comm1 este {1,2} i comm2 este {2,0}. Daca operaia broadcast este sincronizat, atunci apare o dependen ciclic: broadcast n comm2 se termin numai dup broadcast din comm0; broadcast n comm0 se termin numai dup broadcast din comm1; i broadcast n comm1 se termin numai dup broadcast din comm2. Astfel aplicaia se blocheaz. - Operaiile colective trebuiesc executate astfel nct s nu apar dependene ciclice. Exemplu: Utilizare greit:
switch(rank) { case 0: MPI_Bcast(buf1, count, type, 0, comm); MPI_Send(buf2, count, type, 1, tag, comm); break; case 1: MPI_Recv(buf2, count, type, 0, tag, comm, status); MPI_Bcast(buf1, count, type, 0, comm); break;
83
Observaie: - Procesul 0 execut broadcast, urmat de o operaie send blocant. Procesul 1 execut mai nti o operaie receive blocant corespunztoare operaiei send, urmat de un broadcast corespunztor celui din procesul 0. Acest program se poate bloca. Operaia broadcast din procesul 0 se poate bloca pn cnd procesul 1 execut operaia broadcast corespunztoare, i astfel nu este executat operaia send. Procesul 1 se va bloca pe receive i nu va executa operaia broadcast. - Ordinea execuiei operaiilor colective i a celor point-to-point trebuie s fie realizat astfel nct chiar dac operaiile colective i cele point-to-point sunt sincronizate s nu apar blocaje. Exemplu: Un program corect:
switch(rank) { case 0: MPI_Bcast(buf1, count, type, 0, comm); MPI_Send(buf2, count, type, 1, tag, comm); break; case 1: MPI_Recv(buf2, count, type, MPI_ANY_SOURCE, tag, comm, status); MPI_Bcast(buf1, count, type, 0, comm); MPI_Recv(buf2, count, type, MPI_ANY_SOURCE, tag, comm, status); break; case 2: MPI_Send(buf2, count, type, 1, tag, comm); MPI_Bcast(buf1, count, type, 0, comm); break; }
Dou posibile execuii n urma crora programul nu se blocheaz sunt prezentate n figura urmtoare:
84
85
Exemplu:
#include <mpi.h> typedef struct { double real,imag; } complex; void cprod(complex *in, complex *inout, int *len, MPI_Datatype *dptr) { int i; complex c; for (i=0; i<*len; ++i) { c.real=(*in).real * (*inout).real - (*in).imag * (*inout).imag; c.imag=(*in).real * (*inout).imag + (*in).imag * (*inout).real; *inout=c; in++; inout++; } } void main (int argc, char *argv[]) { int rank; int root; complex source,result; MPI_Op myop; MPI_Datatype ctype; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD,&rank); MPI_Type_contiguous(2,MPI_DOUBLE,&ctype); MPI_Type_commit(&ctype); MPI_Op_create(cprod,TRUE,&myop); root=2; source.real=rank+1; source.imag=rank+2; MPI_Reduce(&source,&result,1,ctype,myop,root, MPI_COMM_WORLD); if(rank==root) printf ("PE:%d rezultatul este %lf + %lfi\n", rank,result.real, result.imag); MPI_Finalize(); }
dorit s se poat transmite date care nu sunt omogene, cum sunt structurile, sau care nu sunt continue n memorie, ca de exemplu anumite elemente dintr-un vector. Pentru rezolvarea acestor probleme, MPI pune la dispoziie dou mecanisme: utilizatorul poate defini tipuri de date derivate, care pot fi utilizate n Comunicrile cu MPI derived datatype; un proces poate mpacheta explicit datele necontinui ntr-un buffer continuu i apoi s l trimit, iar procesul care primete datele poate desface datele primite n buffer i s le stocheze n locaii necontinue pack/unpack.
Observaie: newtype reprezint tipul de date obinut prin concatenarea a count copii ale oldtype ca n figura de mai jos:
87
oldtype count = newtype Funcia MPI_TYPE_VECTOR este un constructor mai general care permite multiplicarea unui tip de date n locaii care constau n blocuri aflate la distane egale (vezi figura urmtoare). Fiecare bloc este obinut prin concatenarea unui acelai numr de copii ale tipului de date oldtype. Spaiul dintre blocuri este multiplu de dimensiunea tipului de date oldtype.
88
Funcia MPI_TYPE_HVECTOR este identic cu MPI_TYPE_VECTOR, cu excepia faptului c pasul este dat n bytes i nu n elemente (vezi figura urmtoare). oldtype count = 3, blocklength = 2, newtype Apelul funciei Hvector:
MPI_ Type_hvector ( count, oldtype, newtype) [IN count] [IN blocklength] [IN stride] [IN oldtype] [OUT newtype] blocklength, stride, numrul de blocuri numrul de elemente din fiecare bloc numrul de bytes dintre blocuri tipul de dat iniial tipul de dat nou
Exemplu:
struct Partstruct { int class; /* clasa particulei */ double d[6]; /* coordonatele */ char b[7]; /* informaii suplimentare*/ }; void SendParticles(MPI_Comm comm) { struct Partstruct particle[1000]; int dest, tag; MPI_Datatype Locationtype; MPI_Type_create_hvector(1000, 3, sizeof(struct Partstruct),MPI_DOUBLE, &Locationtype); MPI_Type_commit(&Locationtype); MPI_Send(particle[0].d,1,Loationtype, dest, tag, comm); ... }
89
Funcia MPI_TYPE_INDEXED permite multiplicarea unui tip de date ntr-o secven de blocuri (fiecare bloc este concatenat la tipul de date oldtype), iar fiecare bloc poate conine un numr diferit de copii i poate avea un deplasament diferit (vezi figura urmtoare). Toate deplasamentele blocurilor sunt multipli ai dimensiunii tipului de date oldtype. oldtype count = 3, blocklength = (2,3,1), deplasament = newtype Apelul funciei Indexed:
MPI_ Type_indexed ( count, array_of_blocklengths, array_of_displacements, oldtype,newtype) [IN count] - numrul de blocuri; de asemenea i numrul de intrri n array_of_displacements i array_of_blocklengths - numrul de elemente din fiecare bloc - deplasamentul pentru fiecare bloc, msurat ca numr de elemente - tipul de dat iniial - tipul de dat nou
Observaie: Exist i un constructor de tip Hindexed care are aceeai semnificaie cu Hvector, fiind diferit de acesta doar prin parametri. Funcia MPI_TYPE_STRUCT este un constructor general. n plus generalizeaz funcia Hindexed prin aceea c permite fiecrui bloc s reprezinte o multiplicare a unor tipuri de date diferite (vezi figura de mai jos): 90
MPI_Datatype Particletype; MPI_Datatype type[3]={MPI_CHAR, MPI_DOUBLE, MPI_CHAR}; Int blocklen={0, sizeof(double), 7*sizeof(double)};
91
MPI_Type_create_struct(3, blocklen, disp, type, & Particletype); MPI_Type_commit(&Particletype); MPI_Send(particle,1000, Particletype, dest, tag, comm); ...
Operaia commit creaz tipul de date, care reprezint de fapt descrierea formal a buffer-ului de Comunicare i nu coninutul acestuia. De aceea dup ce un tip de date a fost creat, acesta poate fi reutilizat pentru a comunica schimbarea coninutului buffer-ului sau, de asemenea, pentru schimbarea coninutului mai multor buffer-e cu adrese de nceput diferite. Eliberarea unui tip de date se poate face folosind funcia Free:
MPI_ Type_free (datatype) [INOUT datatype] tipul de date care trebuie eliberat
Funcia MPI_TYPE_FREE marcheaz tipul de date primit ca parametru cu tipul de date pentru dealocare i seteaz tipul de date pe 92
MPI_DATATYPE_NULL. Orice comunicare care utilizeaz acest tip de date se va termina fr erori. Tipurile de date derivate care au fost definite pornind de la tipul de date eliberat nu sunt afectate.
Lungimea mesajelor
Dac un proces primete un mesaj folosind un tip de date definit de utilizator, atunci un apel al funciei MPI_GET_COUNT(status, datatype, count) va returna numrul de copii ale tipului de dat primit. Dac operaia de recepie este MPI_RECV, atunci MPI_GET_COUNT poate returna orice valoare k ,unde 0<= k <= count. Dac funcia va returna valoarea k, atunci numrul de elemente primite este n*k, unde n este numrul de elemente din tipul de date. Pentru determinarea numrului de elemente din tipul de date se poate folosi funcia MPI_GET_ELEMENTS :
MPI_GET_ELEMENT(status, datatype, count) [IN status] - variabil util pentru aflarea dimensiunii, identificatorului de mesaj i a sursei - tipul de date utilizat n operaia receive - numrul de elemente din tipul de date primit
93
/* prelucrarea unui tip de date pentru fiecare tip de union posibil */ MPI_Address( u, &i); MPI_Address( u+1, &j); disp[0] = 0; disp[1] = j-i; type[1] = MPI_UB; type[0] = MPI_INT; MPI_Type_struct(2, blocklen, disp, type, &mpi_utype[0]); type[0] = MPI_FLOAT; MPI_Type_struct(2, blocklen, disp, type, &mpi_utype[1]); for(i=0; i<2; i++) MPI_Type_commit(&mpi_utype[i]); /* Comunicarea efectiv */ MPI_Send(u, 1000, mpi_utype[utype], dest, tag, comm);
94
Fig. Topologia virtual tor bidimensional [10] Trebuie facut distincie ntre topologia virtual a proceselor i topologia fizic (hardware). Descrierea topologiilor virtuale, depinde numai de aplicaie i este independent de main.
Topologii virtuale
Structura comunicrilor pentru un set de procese poate reprezenta un graf, n care nodurile reprezint procesele iar muchiile conecteaz procesele care comunic ntre ele. MPI asigur transmiterea mesajelor ntre perechile de procese din grup. Nu este nevoie ca un canal de comunicare s fie deschis explicit. De aceea, o muchie lips n graful de procese definit de utilizator nu asigur faptul c procesele care nu sunt conectate nu fac schimb mesaje. Aceasta nseamn c aceste conexiuni sunt neglijate n topologia virtual. Aceast strategie implic faptul c topologia nu ofer o cale convenabil pentru stabilirea cilor de comunicare. Muchiile din graful de comunicare nu au ponderi, deci procesele sunt conectate sau nu. Informaiile legate de topologie sunt asociate comunicatorilor. Funciile MPI_GRAPH_CREATE i MPI_CART_CREATE sunt folosite pentru a crea o topologie virtual general (un graf) i topologii carteziene. Aceste funcii sunt operaii colective. Ca i pentru alte apeluri
95
de funcii colective, programul trebuie scris s funcioneze corect indiferent dac apelurile se sincronizeaz sau nu.
Topologii carteziene
Funcia MPI_CART_CREATE poate fi folosit pentru a descrie structuri carteziene de dimensiune arbitrar. Pentru fiecare coordonat se poate specifica dac structura este periodic sau nu. De exemplu, o topologie 1D, este liniar dac nu este periodic i este un inel dac este periodic. Pentru o topologie 2D, avem un dreptunghi, un cilindru sau un tor dup cum o dimensiune este sau nu periodic. Observaie: Un hipercub cu n dimensiuni este un tor cu n dimensiuni cu cte 2 procese pe fiecare coordonat. De aceea nu este necesar un suport special pentru o structur de hipercub. Apelul funciei Cart_create:
MPI_Cart_create(comm_old, ndims, dims, periods, reorder, comm_cart) [IN comm_old] [IN ndims] [IN dims] [IN periods] [IN reorder] - comunicator - numrul de dimensiuni ale topologiei carteziene - tablou de ntregi de dimensiune ndims care specific numrul de procese de pe fiecare dimensiune - tablou de dimensiune ndims care specific dac grila este periodic (true) sau nu (false) pe fiecare dimensiune - rangurile pot fi reordonate (true) sau nu (false) [OUT comm_cart] comunicator cu noua topologie cartezian
Pentru topologiile carteziene, funcia MPI_DIMC_CREATE ajut utilizatorul s realizeze o distribuire echilibrat a proceselor pe fiecare 96
direcie de coordonate. O posibilitate ar fi mprirea tuturor proceselor ntr-o topologie n-dimensional. Apelul funciei este:
MPI_ Dims_create(nnodes, ndims, dims)
[IN nnodes] [IN ndims] [INOUT dims]
- numrul de noduri - numrul de dimensiuni ale topologiei - tablou de ntregi care specific numrul de noduri pe fiecare dimensiune
Pentru fiecare dims[i] setat de apelul funciei MPI_ Dims_create, dims[i] va fi ordonat descresctor. Tabloul dims este potrivit pentru a fi utilizat ca intrare n apelul funciei MPI_Cart_create. Exemplu:
dims nainte de apel (0,0) (0,0) (0,3,0) (0,3,0) apelul funciei MPI_DIMS_CREATE(6, 2, dims) MPI_DIMS_CREATE(7, 2, dims) MPI_DIMS_CREATE(6, 3, dims) MPI_DIMS_CREATE(7, 3, dims) dims la return (3,2) (7,1) (2,3,1) apel eronat
Odat topologia stablit, pot fi necesare interogri cu privire la topologie. Aceste funcii sunt urmtoarele i sunt apelate local:
MPI_Cartdim_get(comm, ndims) [IN comm] [OUT ndims] - comunicator cu structura cartezian - numrul de dimensiuni al structurii carteziene
97
Pentru un grup de procese cu o structur cartezian, funcia MPI_CART_RANK transform coordonatele logice ale procesului n rangul procesului aa cum ele sunt utilizate n comunicrile point-topoint. De exemplu pentru coords = (1,2) vom avea rank = 6.
MPI_Cart_get(comm, maxdims, dims, periods, coords) [IN comm] [IN maxdims] [OUT dims] [OUT periods] [OUT coords] - comunicator cu structura cartezian - lungimea vecorilor dims, periods, i coords n program - numrul de procese pe fiecare dimesiune cartezian - periodicitatea ( true/ false) pentru fiecare dimesiune - coordonatele procesului care apleaz funcia in structura cartezian
Pentru fiecare dimensiune i cu periods(i) = true, dac coordonata coords(i) este n afara limitelor, adic coords(i) < 0 sau coords(i) >= dims(i), este adus napoi n intervalul 0 <= coords(i) < dims(i). Dac topologia este una periodic n ambele dimensiuni (tor), atunci coords=(4,6) va fi de asemenea rank=6. Coordonatele din afara limitelor genereaz eroare pentru dimensiuni neperiodice. Exemplu:
#include <mpi.h>
98
/* Rulare cu 12 procese */ void main(int argc, char *argv[]) { int rank; MPI_Comm vu; int dim[2],period[2],reorder; int coord[2],id; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD,&rank); dim[0]=4; dim[1]=3; period[0]=TRUE; period[1]=FALSE; reorder=TRUE; MPI_Cart_create(MPI_COMM_WORLD, 2, dim, period, reorder, &vu); if(rank==5){ MPI_Cart_coords(vu, rank, 2, coord); printf("P:%d My coordinates are %d d\n", rank, coord[0],coord[1]); } if(rank==0) { coord[0]=3; coord[1]=1; MPI_Cart_rank(vu, coord, &id); printf("The processor at position (%d, %d) has rank%d\n",coord[0], coord[1], id); } MPI_Finalize(); }
Programul va afia:
The processor at position (3,1) has rank 10 P:5 My coordinates are 1 2
99
Funcia MPI_GRAPH_CREATE are ca valoare de return un handler ctre noul comunicator care are ataate informaiile privind topologia grafului . Parametrii nnodes, index i edges definesc structura grafului. Numrul total de valori din index este nnodes i numarul de valori din edges este egal cu numrul de muchii ale grafului. Examplu: S presupunem c procesele 0,1,2,3 au urmtoarea matrice de adiacen: proces 0 1 2 3 vecini 1,3 0 3 0,3
Argumentele de intrare sunt: nnodes=4, index=2,3,4,6 i edges=1,3,0,3,0,2 Urmtoarele informaii legate de structura topologiei sunt stocate de comunicator: - Tipul topologiei (cartezian sau graf) - Pentru o topologie cartezian: ndims (numrul de dimensiuni), dims (numrul de procese pe direcia de coordonate), periods (periodicitatea informaiei), own_position (poziia din topologia creat).
- Pentru o topologie de tip graf: index, muchii. Pentru o structur de tip graf, numrul de noduri este egal cu numrul de procese din grup. De aceea numrul nodurilor nu trebuie memorat separat. Funciile MPI_GRAPHDIMS_GET i MPI_GRAPH_GET primesc informaii privind topologia grafului care au fost asociate cu un comunicator de ctre funcia MPI_GRAPH_CREATE. Informaiile 100
furnizate de MPI_GRAPHDIMS_GET pot fi utilizate pentru dimensionarea corect a vectorilor index i edges pentru a realiza un apel MPI_GRAPH_GET.
MPI_ Graphdims_get (comm, nnodes, nedges) [ IN comm] [ OUT nnodes] [ OUT nedges] - comunicator cu grupul care are structura de graf - numrulde noduri din graf (acelai cu numrul proceselor din grup) - numrul de muchii din graf
Funciile MPI_ Graph_neighbors_count i MPI_Graph_neighbors furnizeaz informaii suplimentare pentru o topologie de tip graf.
MPI_Graph_neighbors_count (comm, rank, nneighbors) [ IN comm] [ IN rank] [ OUT nneighbors] - comunicator cu topologia grafului - rangul procesului din grupul comm - numrul vecini ai procesului specificat
101
comunicator cu topologia grafului rangul procesului din grupul comm mrimea tabloului cu vecinii rangurile proceselor care au ca vecini procesul specificat
Funcia MPI_Topo_test ntoarce tipul topologiei asignate unui comunicator. Aceste valori pot fi:
[ MPI_GRAPH] [ MPI_CART] [ MPI_UNDEFINED] - topologie de tip graf - topologie de tip cartezian - nici o topologie
102
Crearea si compilarea unei aplicaii folosind VisualC++ 6.0 [5]: 1. Deschidere MS Developer Studio - Visual C++ i crearea unui proiect nou.
103
2. Se alege Project->Settings (sau Alt +F7 ) 3. Se schimb setrile pentru a utiliza biblioteca cu fire de execuie.
104
5. Se modific calea pentru includerea fisierelor obiect asociate bibliotecii mpi.h (de exemplu: C:\Program Files\MPICH\SDK\lib )
105
106
107
Crearea i compilarea unei aplicaii pe sisteme Unix si Linux [4]: MPICH: Compilarea folosind GNU C Compiler (gcc):
cc o prog prog.c I/usr/local/mpi/include \ L/usr/local/mpi/lib -lmpi
Pentru rularea unei aplicatii trebuie pornit mediul MPI (implicit pe localhost)
lamboot v
Pentru a rula aplicatia pe mai multe sisteme trebuie creat un fisier 1 lamhosts in care se scriu adresele IP ale sistemelor sau numele : calc1.domeniu.ro 192.168.247.10 calc2.domeniu.ro 192.168.247.11 calc3.domeniu.ro 192.168.247.12 calc4.domeniu.ro 192.168.247.13 Linia de comanda se va modifica corespunzator:
lamboot v lamhosts
Rularea programelor:
mpirun-lam -np 4 prog
108
109
110
/**************** Calculul prefixelor *****************/ /* Compilare : mpicc -o xxx xxx.c Executie : mpirun -np 8 xxx Observatii: - Algoritmul se va rula doar pt un nr de procese(noduri) N+1 putere a lui 2,minim 8(8, 16,...) - Nodul 0 nu participa la comunicare si nu face parte din arborele prefixelor */ #include <stdio.h> #include "mpi.h" #define N 7 //numarul de noduri; size va trebui sa //fie >= N+1
main(int argc, char **argv) { int A[N], B[N]; int k, j, aux, aux1, aux2; int myrank, size; MPI_Status status; //initializari MPI MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &myrank); MPI_Comm_size(MPI_COMM_WORLD, &size); if(myrank == 0) printf("\n***** Calculul prefixului paralel *****\n\n"); /********* Calcul Prefixe ********/ //nodurile frunza if(myrank >= size/2) { //informatia initiala o constitue rangurile //nodurilor A[myrank] = myrank; //se trimite nodului parinte informatia - inceputul //fazei 1: parcurgerea arborelui de jos in sus MPI_Send(&A[myrank], 1, MPI_INT, myrank/2, 99, MPI_COMM_WORLD);
111
//se asteapta de la nodul parinte primirea B-ului MPI_Recv(&aux, 1, MPI_INT, myrank/2, 99, MPI_COMM_WORLD, &status); B[myrank] = aux; //nodurile pare asteapta si informatia de la nodul //impar imediat urmator(de pe acelasi nivel) if(myrank%2==0) MPI_Recv(&B[myrank], 1, MPI_INT, myrank+1, 99, MPI_COMM_WORLD, &status); //nodurile impare efectueaza calculul prefixului si //trimit rezultatul in stanga(pe acelasi nivel) la //nodul par inferior else { aux = B[myrank] - A[myrank]; MPI_Send(&aux, 1, MPI_INT, myrank-1, 99, MPI_COMM_WORLD); } } //nodurile de pe nivelele superioare, cu exceptia //radacinii si a nodului 0 care nu apartine arborelui if(myrank < size/2 && myrank !=0 && myrank!=1) { //se asteapta primirea datelor de la cei 2 fii MPI_Recv(&aux1, 1, MPI_INT, myrank*2, 99, MPI_COMM_WORLD, &status); MPI_Recv(&aux2, 1, MPI_INT, myrank*2+1, 99, MPI_COMM_WORLD, &status); //se calculeaza A-ul nodului care este trimis si //parintelui A[myrank] = aux1+aux2; MPI_Send(&A[myrank], 1, MPI_INT, myrank/2, 99, MPI_COMM_WORLD); //se asteapta B-ul de la parinte MPI_Recv(&aux, 1, MPI_INT, myrank/2, 99, MPI_COMM_WORLD, &status); //nodurile pare primesc datele de la cele impare //superioare(+1) de pe acelasi nivel si fac calculul if(myrank%2==0) { MPI_Recv(&aux1, 1, MPI_INT, myrank+1, 99, MPI_COMM_WORLD, &status); B[myrank] = aux - aux1; } //nodurile impare isi actualizeaza informatia legata //de B si trimit datele celor pare pt a face //calculele else {
112
B[myrank] = aux; MPI_Send(&A[myrank], 1, MPI_INT, myrank-1, 99, MPI_COMM_WORLD); } //nodurile parinte trimit catre cei 2 fii datele MPI_Send(&B[myrank], 1, MPI_INT, myrank*2, 99, MPI_COMM_WORLD); MPI_Send(&B[myrank], 1, MPI_INT, myrank*2+1, 99, MPI_COMM_WORLD); } //nodul radacina - nu face comunicarea pe orizontala if(myrank==1) { //se asteapta de la nodurile 2 si 3 A-urile MPI_Recv(&aux1, 1, MPI_INT, myrank*2, 99, MPI_COMM_WORLD, &status); MPI_Recv(&aux2, 1, MPI_INT, myrank*2+1, 99, MPI_COMM_WORLD, &status); A[myrank] = aux1+aux2; //se calculeaza B-ul si se incepe parcurgerea //arborelui de sus in jos - faza a 2a : calculul B//urilor B[myrank] = A[myrank]; MPI_Send(&B[myrank], 1, MPI_INT, myrank*2, 99, MPI_COMM_WORLD); MPI_Send(&B[myrank], 1, MPI_INT, myrank*2+1, 99, MPI_COMM_WORLD); } //la final se verifica rezultatele if(myrank == 0) printf("Rezultatele din fiecare nod la finalul algoritmului:\n\n"); //toate procesele sunt asteptate sa termine MPI_Barrier(MPI_COMM_WORLD); //nodul 0 nu e implicat in algoritm si nu afiseaza if(myrank!=0) printf("P%d: A[%d]=%d B[%d]=%d\n", myrank, myrank, A[myrank], myrank, B[myrank]); MPI_Finalize(); }
113
/********** Scurtcircuitarea; Calcul ranguri **********/ /* Compilare : mpicc -o xxx xxx.c -lm Executie : mpirun -np 8 xxx Observatii: - Numarul de procese poate fi oricat de mare(in program maxim 99) - log2(x) = log(x)/log(2) */ #include <math.h> #include <stdio.h> #include "mpi.h" main(int argc, char **argv) { //p este lista de legaturi - utila pt a vedea in fiecare //pas al algoritmului modificarea legaturilor int p[100], i; int rank, r[100]; int myrank, size; MPI_Status status; int succ=0; int root = 0; //initializari MPI MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &myrank); MPI_Comm_size(MPI_COMM_WORLD, &size); if(myrank == 0) printf("\n***** Scurtcircuitarea; Calcul ranguri *****\n\n"); MPI_Barrier(MPI_COMM_WORLD); /****** Calcul ranguri *******/ //construirea legaturile initiale intre noduri if(myrank==0) succ = 0; else succ = myrank-1; //initial nodul 0 are rang 0;celelalte noduri au rangul 1 if(succ != myrank) rank = 1; else rank = 0;
114
//rootul colecteaza informatiile despre succesori si //ranguri de la celelate noduri MPI_Gather(&succ, 1, MPI_INT, p, 1, MPI_INT, root, MPI_COMM_WORLD); MPI_Gather(&rank, 1, MPI_INT, r, 1, MPI_INT, root, MPI_COMM_WORLD); //si apoi le redistribue inapoi MPI_Bcast(p, size, MPI_INT, root, MPI_COMM_WORLD); MPI_Bcast(r, size, MPI_INT, root, MPI_COMM_WORLD); //afisare inainte de inceperea algoritmului printf("P%d - are inital rangul %d si lista de legaturi: ", myrank, rank); for(i=0; i<size; i++) printf("%d ", p[i]); printf("\n"); //scurtcircuitare - la fiecare pas comunica nodurile //aflate la distanta i //bucla se executa de log2(size) ori => complexitatea //O(log(nr_noduri)) for(i=0; i<log(size)/log(2); i++) { //fiecare nod actualizeaza local informatia if(succ != p[succ]) { rank = rank + r[succ]; succ = p[succ]; } //actualizarile locale sunt colectate de root MPI_Gather(&succ, 1, MPI_INT, p, 1, MPI_INT, root, MPI_COMM_WORLD); MPI_Gather(&rank, 1, MPI_INT, r, 1, MPI_INT, root, MPI_COMM_WORLD); //iar acesta le redistribuie nodurilor in vederea //pasului urmator MPI_Bcast(p, size, MPI_INT, root, MPI_COMM_WORLD); MPI_Bcast(r, size, MPI_INT, root, MPI_COMM_WORLD); } //afisare dupa terminarea algoritmului printf("P%d - are la final rangul %d si lista de legaturi: ", myrank, rank); for(i=0; i<size; i++) printf("%d ", p[i]); printf("\n"); MPI_Finalize(); }
115
/********* Difuzarea unu-la-toti pe hipercub *********/ /* Compilare : mpicc -o xxx xxx.c -lm Executie : mpirun -np 8 xxx Observatii: - size trebuie sa fie putere a lui 2 (se lucreaza pe hipercub) */ #include #include #include #include <stdio.h> <string.h> <math.h> "mpi.h"
main(int argc, char **argv) { char message[20]; int myrank, size, p; double d; MPI_Status status; int mask, i, msg_dest, msg_src; int rez; //initializari MPI MPI_Init (&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &myrank); MPI_Comm_size(MPI_COMM_WORLD, &size); if(myrank == 0) printf("\n***** Difuzarea unu-la-toti pe hipercub *****\n\n"); //procesul root pregateste mesajul de trimis; celelalte //procese trec in asteptare if(myrank == 0) strcpy(message, "Mesajul de trimis"); else strcpy(message, "Mesajul nu a ajuns"); if(myrank == 0 ) printf("Inainte de inceperea algoritmului:\n"); //afisare la inceput MPI_Barrier(MPI_COMM_WORLD); printf("P %d initial: %s\n", myrank, message); MPI_Barrier(MPI_COMM_WORLD); //se calculeaza numarul pasilor == numarul dimensiunilor //cubului d = log(size)/log(2);
116
/***** ONE_TO_ALL_BC *****/ //se seteaza toti bitii mastii pe 1 mask = pow(2, d) - 1; for(i=d-1; i>=0; i--) { //se seteaza bitul i al mastii pe 0 p = (int)pow(2, i); // p este de fapt 2^i mask = mask ^ p; if((myrank & mask) == 0) { //daca bitii inferiori i sunt 0, nodul trimite if((myrank & p) == 0) { msg_dest = myrank ^ p; MPI_Send(message, strlen(message), MPI_CHAR, msg_dest, 99, MPI_COMM_WORLD); } //daca nu, primeste else { msg_src = myrank ^ p; MPI_Recv(message, strlen(message), MPI_CHAR, msg_src, 99, MPI_COMM_WORLD, &status); } } //verificare dupa fiecare etapa if(myrank == 0 ) printf("\nDupa faza %d:\n", d-i); MPI_Barrier(MPI_COMM_WORLD); printf("P %d : %s\n", myrank, message); MPI_Barrier(MPI_COMM_WORLD); } MPI_Barrier(MPI_COMM_WORLD); if(myrank == 0 ) printf("\nLa finalul algoritmului:\n"); MPI_Barrier(MPI_COMM_WORLD); //verificarea la final - mesajul initial trebuie sa fie la //toate celelalte noduri de pe hipercub printf("P %d final: %s\n", myrank, message); MPI_Finalize(); }
117
/********** Difuzarea toti-la-toti pe hpercub **********/ /* Compilare: mpicc -o xxx xxx.c -lm Executie: mpirun -np 8 xxx Observatii: - size trebuie sa fie putere a lui 2 (se lucreaza pe hipercub) - log2(x) = log(x)/log(2) */ #include #include #include #include <stdio.h> <string.h> <math.h> "mpi.h"
#define N 8 //numarul maxim de noduri main(int argc, char **argv) { //vectorii de mesaje: lungime fiecare mesaj(20) * N + 1 char message[21], recv_mes[161], all_mes[161], aux[161]; int myrank, size, p; double d; MPI_Status status; int i, j, msg_partener;
//initializari MPI MPI_Init (&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &myrank); MPI_Comm_size(MPI_COMM_WORLD, &size); if(myrank == 0) printf("\n***** Difuzarea toti-la-toti pe hpercub *****\n\n"); //se pregateste mesajul de trimis strcpy(message, "Mesajul de trimis //'\0' trebuie sa fie pe pozitia 20 message[18] = '0'+ myrank; ");
//toate celelalte procese au numai '_' in mesaje la //momentul initial for(i=0; i<160; i++) { all_mes[i] = '_'; recv_mes[i] = '_'; aux[i] = '_'; } all_mes[160] = '\0'; recv_mes[160] = '\0';
118
//fiecare nod isi pregateste propriul mesaj in pozitia //corespunzatoare din vectorul de mesaje for(i=0; i<19; i++) all_mes[20*myrank+i] = message[i]; if(myrank == 0 ) printf("Inainte de inceperea algoritmului:\n"); MPI_Barrier(MPI_COMM_WORLD); printf("\tP %d : %s**\n\n", myrank, all_mes); //dimensiunea hipercubului d = log(size)/log(2); /******* ALL_TO_ALL_BC *******/ for(i=0; i<=d-1; i++) { //comunicarea cu nodul partener - la fiecare pas se //lucreaza pe cate o dimensiune msg_partener = myrank ^ (int)(pow(2, i)); MPI_Send(all_mes, 161, MPI_CHAR, msg_partener, 99, MPI_COMM_WORLD); MPI_Recv(recv_mes, 161, MPI_CHAR, msg_partener, 99, MPI_COMM_WORLD, &status); //mesajele primite de la partener sunt copiate in //pozitiile libere for(j=0; j<160; j++) if(all_mes[j]=='_' ) all_mes[j] = recv_mes[j]; } //afisarea mesajelor din noduri pentru verificare if(myrank == 0 ) printf("\n\nLa finalul algoritmului:\n"); MPI_Barrier(MPI_COMM_WORLD); printf("P %d: %s\n", myrank, all_mes); MPI_Finalize(); }
119
/****************** Sortarea bitonica ******************/ /* Compilare : mpicc -o xxx xxx.c -lm Executie : mpirun -np 8 xxx Observatii: - N_MAX(8 in program) reprezinta numarul elementelor de sortat si trebuie sa fie egal cu numarul de procese - d=0 sortare crescatoare, d=1 descrescatoare */ #include #include #include #include <stdio.h> <string.h> <math.h> "mpi.h"
#define N_MAX 8 //N_MAX tb sa fie egal cu nr de procese /***** algortim de interclasare a 2 secvente bitonice ***/ void interclasare_bitonica(int A[N_MAX], int pi, int pf, int d,int rank) { int b, aux; int j, i, new_val; MPI_Status status; b = pf - pi+1; //daca se compara doar 2 elemente, doar nodul 0 sorteaza //elementele if(b == 2) { if(rank==0) { if(A[pi] < A[pf]) { if(d==1) { aux = A[pf]; A[pf] = A[pi]; A[pi] = aux; } } else { if(d==0) { aux = A[pf]; A[pf] = A[pi]; A[pi] = aux; }
120
else
//pe aceasta ramura lucreaza in paralel toate //procesele { //actualizarea valorilor fiecarui nod MPI_Barrier(MPI_COMM_WORLD); MPI_Bcast(A, N_MAX, MPI_INT, 0, MPI_COMM_WORLD); new_val = A[rank]; for(j=b/2-1; j>=0; j--) { //actualizarea vectorilor la toate procesele MPI_Bcast(A, N_MAX, MPI_INT, 0, MPI_COMM_WORLD); //mai intai se sorteaza cate 2 valori; //sortarea e realizata de procesele mici(de //rang inferior) if(rank==pi+j)//proces inferior { if(A[pi+j] < A[pi+b/2+j]) { if(d==1) { aux = A[pi+b/2+j]; A[pi+b/2+j] = A[pi+j]; A[pi+j] = aux; } } else { if(d==0) { aux = A[pi+b/2+j]; A[pi+b/2+j] = A[pi+j]; A[pi+j] = aux; } } new_val = A[pi+j]; //procesele mai mici trimit rezultatul //catre cele superioare MPI_Send(&A[pi+b/2+j], 1, MPI_INT, pi+b/2+j, 99, MPI_COMM_WORLD); } if(rank == pi+b/2+j)//proces superior { //procesele superioare primesc
121
} //dupa comparatii, se reactualizeaza datele din //noduri MPI_Barrier(MPI_COMM_WORLD); MPI_Gather(&new_val, 1, MPI_INT, A, 1, MPI_INT, 0, MPI_COMM_WORLD); MPI_Bcast(A, N_MAX, MPI_INT, 0, MPI_COMM_WORLD); MPI_Barrier(MPI_COMM_WORLD); interclasare_bitonica(A, pi, pi+b/2-1, d,rank); interclasare_bitonica(A, pi+b/2, pf, d,rank); }
/******** algoitmul Batcher de sortare bitonica *********/ void sortare_bitonica(int A[N_MAX], int pi, int pf, int d,int rank) { int b,i, aux; b = pf - pi+1; //daca se compara 2 elemente, if(b == 2) { if(rank==0) { if(A[pi] < A[pf]) { if(d==1) { aux = A[pf] A[pi] } } else { if(d==0) { aux = A[pf] A[pi] } } } } else sortarea o face nodul 0
122
{ sortare_bitonica(A, pi,pi+b/2-1, 0, rank); sortare_bitonica(A, pi+b/2, pf, 1, rank); //se reactualizeaza informatiile de la toate //nodurile MPI_Bcast(A, N_MAX, MPI_INT, 0, MPI_COMM_WORLD); interclasare_bitonica(A, pi, pf, d, rank); }
/********************************************************/ main(int argc, char **argv) { int myrank, size; MPI_Status status; int id, i, d; //numerele de sortat int A[N_MAX]= {7, 5, 9, 10, 2, 6, 3, 8}; //initializari MPI MPI_Init (&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &myrank); MPI_Comm_size(MPI_COMM_WORLD, &size); if(myrank==0) { printf("\n***** Sortarea bitonica (Algoritmul lui Batcher) *****\n\n"); printf("Inainte de sortare:\n"); for(i=0; i<N_MAX; i++) printf("A[%d]: %d\n", i, A[i]); } //d=0 crescator, d=1 descrescator d = 0; //sortarea efectiva sortare_bitonica(A, 0, N_MAX-1, d, myrank); if(myrank==0) { printf("\n\nDupa sortare:\n"); for(i=0; i<N_MAX; i++) printf("A[%d]: %d\n", i, A[i]); } MPI_Finalize(); }
123
/***************** Sortarea prin numarare **************/ /* Compilare : mpicc -o xxx xxx.c -lm Executie : mpirun -np 8 xxx Observatii: - numarul de procese = 2 * nr_elem - numarul de elemente trebuie sa fie putere a lui 2; eventual se poate completa vectorul de sortat cu 0 */ #include #include #include #include <stdio.h> <string.h> <math.h> "mpi.h" 4 //numarul maxim de elemente
#define N_MAX
main(int argc, char **argv) { int myrank, size; MPI_Status status; int A[N_MAX], A_sortat[N_MAX]; int R[2*N_MAX-1][N_MAX], Rsend[N_MAX], Rrecv_1[N_MAX], Rrecv_2[N_MAX]; int P[N_MAX], poz; int i, j; //valorile initale A[0] = 2; A[1] = 6; A[2] = 3; A[3] = 8; //initializari MPI MPI_Init (&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &myrank); MPI_Comm_size(MPI_COMM_WORLD, &size); //afisare initiala if(myrank==0) { printf("\n***** Sortarea prin numarare *****\n\n"); printf("P %d - lista initiala: ", myrank); for(i=0; i<N_MAX; i++) printf("%d ", A[i]); }
124
//Faza 1: inceperea sortarii - determinarea rangurilor //relative; //pentru usurarea comunicarii,tabloul bidimensional este //conceput ca un arbore; //ultimele N noduri(frunze) fac calculul initial if(myrank >=size/2) { for(i=0; i<N_MAX; i++) { if(A[myrank-size/2] <= A[i]) R[myrank][i] = 1; else R[myrank][i] = 0; } } //Faza 2: calcularea pozitiilor P[j] prin : sum(R[j],+); //P[j] = R[1,j]; //frunzele trimit la parinti vectorii calculati if(myrank >= size/2) MPI_Send(R[myrank], N_MAX, MPI_INT, myrank/2, 99, MPI_COMM_WORLD); //parintele prelucreaza datele else { //pentru noduri diferite de radacina arborelui(nodul //0 nu este folosit) if((myrank < size/2) && (myrank!=0) && (myrank!=1)) { //primeste de la cei 2 fii MPI_Recv(&Rrecv_1, N_MAX, MPI_INT, myrank*2, 99, MPI_COMM_WORLD, &status); MPI_Recv(&Rrecv_2, N_MAX, MPI_INT, myrank*2+1, 99, MPI_COMM_WORLD, &status); //actualizeaza vectorul propriu prin sumare for(i=0; i<N_MAX; i++) R[myrank][i] = Rrecv_1[i] + Rrecv_2[i]; //dupa care il trimite la parinte sau MPI_Send(R[myrank], N_MAX, MPI_INT, myrank/2, 99, MPI_COMM_WORLD); } else //nodul radacina este terminal; el nu mai //efectueaza transmisia { if(myrank==1) { //primeste de la fiii 2 si 3
125
MPI_Recv(&Rrecv_1, N_MAX, MPI_INT, myrank*2, 99, MPI_COMM_WORLD, &status); MPI_Recv(&Rrecv_2, N_MAX, MPI_INT, myrank*2+1, 99, MPI_COMM_WORLD, &status); //calculeaza rezultatul final for(i=0; i<N_MAX; i++) R[myrank][i] = Rrecv_1[i] + Rrecv_2[i]; }
MPI_Barrier(MPI_COMM_WORLD); //nodul 1 distribuie nodurilor pozitiile pe care trebuie //sa se afle elementele lor //informatia va fi folosita doar de primele N_MAX noduri MPI_Bcast(R[1], N_MAX, MPI_INT, 1, MPI_COMM_WORLD); //fiecare proces determina pozitia elementului sau // se pleaca de la 0 if(myrank < N_MAX) poz = R[1][myrank]-1; //nodul 0 actualizeaza imediat vectorul final if(myrank == 0) A_sortat[poz] = A[0]; //el astepta apoi de la urmatoarele N_MAX-1 noduri : intai //pozitia si apoi valoarea de inserat if(myrank==0) { for(i=1;i<N_MAX; i++) { MPI_Recv(&P[i],1, MPI_INT, i, 99, MPI_COMM_WORLD, &status); MPI_Recv(&A_sortat[P[i]], 1, MPI_INT, i, 99, MPI_COMM_WORLD, &status); } } else //celelalte noduri trimit intai pozitia si apoi //valoarea de inserat { if(myrank < N_MAX) { MPI_Send(&poz, 1, MPI_INT, 0, 99, MPI_COMM_WORLD); MPI_Send(&A[myrank], 1, MPI_INT, 0, 99, MPI_COMM_WORLD);
126
//afisarea finala if(myrank==0) { printf("\nP %d - lista sortata: ", myrank); for(i=0; i<N_MAX; i++) printf("%d ", A_sortat[i]); printf("\n"); } MPI_Finalize(); }
127
/********** Sortarea par-impar (ODD_EVEN_SORT) *********/ /* Compilare : mpicc -o xxx xxx.c Executie : mpirun -np 8 xxx Observatii: - Numarul de procese este egal cu numarul elementelor de sortat(acesta poate fi schimbat in program) */ #include <stdio.h> #include <string.h> #include "mpi.h" #define N 8 //numarul elementelor de sortat
main(int argc, char **argv) { int myrank, size; MPI_Status status; int id, i, n, cur_number, aux_number; int V[N]={3, 2, 3, 8, 5, 6, 4, 1}; //initializari MPI MPI_Init (&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &myrank); MPI_Comm_size(MPI_COMM_WORLD, &size); //afisarea initiala if(myrank==0) printf("\n***** Sortarea par-impar (ODD_EVEN_SORT) *****\n\n"); /******* Odd_Even_Sort **********/ id = myrank+1; n = size; //din vectorul initial, fiecare nod vede un singur element cur_number = V[myrank]; //nodurile afiseaza numerele proprii if(id==1) printf("Inainte de sortare:\n"); MPI_Barrier(MPI_COMM_WORLD); printf("P[%d]: %d\n", id, cur_number); //pt comparatie ,procesele mai mari trimit elementele //catre cele mai mici for(i=1; i<=n; i++) { //sincronizare necesara pt ca toate procesele sa //inceapa o noua etapa concomitent MPI_Barrier(MPI_COMM_WORLD);
128
if(i % 2 == 1)//i impar { //id impar - proces "inferior" if(id % 2 == 1) { //primeste de la corespondent MPI_Recv(&aux_number, 1, MPI_INT, (id1)+1, 99, MPI_COMM_WORLD, &status); //dupa calcul, trimite procesului //corespondent maximul; se pastreaza //minimul if(aux_number < cur_number) { MPI_Send(&cur_number, 1, MPI_INT, (id-1)+1, 99, MPI_COMM_WORLD); cur_number = aux_number; } else MPI_Send(&aux_number, 1, MPI_INT, (id-1)+1, 99, MPI_COMM_WORLD); } else//proces "superior" { //trimite valoarea sa pentru a putea fi //comparata MPI_Send(&cur_number, 1, MPI_INT, (id1)-1, 99, MPI_COMM_WORLD); //se primeste maximul MPI_Recv(&cur_number, 1, MPI_INT, (id-1) -1, 99, MPI_COMM_WORLD, &status); } } else//i par { //primul si ultimul nod nu comunica in aceasta //etapa if((id!=1) && (id!=N)) { if(id % 2 == 0)//proces "inferior" { //se primeste valoarea //corespondentului "superior" MPI_Recv(&aux_number, 1, MPI_INT, (id-1)+1, 99, MPI_COMM_WORLD, &status);
129
//se trimite maximul si se //pastreaza minimul if(aux_number < cur_number) { MPI_Send(&cur_number, 1, MPI_INT, (id-1)+1, 99, MPI_COMM_WORLD); cur_number = aux_number; } else MPI_Send(&aux_number, 1, MPI_INT, (id-1)+1, 99, MPI_COMM_WORLD); } else//proces "superior" { //trimite valoare sa si apoi //receptioneaza maximul de la //corespondent MPI_Send(&cur_number, 1, MPI_INT, (id-1)-1, 99, MPI_COMM_WORLD); MPI_Recv(&cur_number, 1, MPI_INT, (id-1)-1, 99, MPI_COMM_WORLD, &status); } }
//afisarea finala if(id == 1) printf("\n\nDupa sortare:\n"); MPI_Barrier(MPI_COMM_WORLD); printf("P[%d]: %d\n", id, cur_number); MPI_Finalize(); }
130
/************ Sortare bitonica pe hipercub ************/ /* Compilare : mpicc -o xxx xxx.c -lm Executie : mpirun -np 8 xxx Observatii: - Numarul de elemente de sortat tb sa fie egal cu numarul de procese(8 in program); - Deoarece se lucreaza pe hipercub, numarul de procese trebuie sa fie putere a lui 2. - Secventa de la care se pleaca tb sa fie bitonica - Sortarea se realizeaza: d=0 crescator, d=1 descrescator */ #include <stdio.h> #include <string.h> #include <math.h> #include "mpi.h" #define N_MAX 8 // egal cu numarul de procese
/****** comunicarea pe o anumita dimensiune : E ********/ void E(int A[N_MAX], int dim, int myrank, int d) { int msg_partener; int aux, data_recv, data_send; MPI_Status status; //determinarea partenerului de comunicatie msg_partener = myrank ^ (int)(pow(2, dim)); //nodurile mai mici efectueaza calculele;tot ele pastreaza //minimul daca d = 0/maximul pentru d=1 if(myrank < msg_partener) { MPI_Recv(&data_recv, 1, MPI_INT, msg_partener, 99, MPI_COMM_WORLD, &status); if(d == 0) //sortare crescatoare { if(data_recv < A[myrank]) //datele trebuie interschimbate { data_send = A[myrank]; A[myrank] = data_recv; } else data_send = data_recv; } else //sortare descrescatoare { if(data_recv > A[myrank]) //datele trebuie interschimbate {
131
else
data_send = data_recv; } //se trimite partenerului de comunicatie rezultatul //calculelor MPI_Send(&data_send, 1, MPI_INT, msg_partener, 99, MPI_COMM_WORLD); } //nodurile superioare trimit datele si asteapta //rezultatul comparatiei { //se trimite data MPI_Send(&A[myrank], 1, MPI_INT, msg_partener, 99, MPI_COMM_WORLD); //si se asteapta rezultatul calculelor MPI_Recv(&A[myrank], 1, MPI_INT, msg_partener, 99, MPI_COMM_WORLD, &status); }
else
/************ procesul de combinare M ******************/ // necesita utilizarea succesiva a dimensiunilor E void Merge(int A[N_MAX], int pas, int myrank, int d) { int i; for(i=pas-1; i>=0; i--) { if(myrank == 0) printf("Se comunica pe dimensiunea %d.\n", i+1); E(A, i, myrank, d); } } /** sortarea bitonica pe cubul binar multidimensional ***/ void BitonicMergeSort(int A[N_MAX], int dim, int myrank, int d) { int i; for(i=dim-1; i>=0; i--) { if(myrank == 0) printf("\nFaza %d de combinare.\n", i); Merge(A, i+1, myrank, d);
132
/********************************************************/ main(int argc, char **argv) { int myrank, size; MPI_Status status; int id, i, d; double dim; //vectorul de sortat int A[N_MAX]={2, 3, 4, 8, 7, 6, 5, 1}; //initializari MPI MPI_Init (&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &myrank); MPI_Comm_size(MPI_COMM_WORLD, &size); //dim = log2(size) dim = log(size)/log(2); if(myrank==0) { printf("\n***** BitonicMergeSort pe hypercube *****\n\n"); printf("Inainte de sortare:\n"); for(i=0; i<N_MAX; i++) printf("A[%d]: %d\n", i, A[i]); } //d=0 crescator, d=1 descrescator d = 0; //sortarea efectiva BitonicMergeSort(A, dim, myrank, d); //afisarea rezultatelor if(myrank==0) printf("\n\nDupa sortare:\n"); //fiecare nod afiseaza informatiile care le are printf("A[%d]: %d\n", myrank, A[myrank]); MPI_Finalize(); }
133
/************* Sortare rapida pe hipercub ***************/ /* Compilare : mpicc -o xxx xxx.c -lm Executie : mpirun -np 8 xxx Observatii: - size trebuie sa fie putere a lui 2(se lucreaza pe hipercub) - Numarul maxim de elemente de sortat = 20(poate fi schimbat in program) - Modul de alegere a pivotului este media aritmetica; daca numerele de sortat au o abatere mare, vor fi noduri care la final nu vor avea elemente */ #include #include #include #include <stdio.h> <string.h> <math.h> "mpi.h" 20 // nr maxim de elemente ale vectorului
#define N
/*************** partitionare vector *****************/ //impartirea lui B in 2 subsiruri B1 si B2 a.i. //B1 <= pivot <= B2 void Partition(int B[N+1], int B1[N+1], int B2[N+1], int pivot) { int i, nr_b1, nr_b2; nr_b1 = 0; //nr de elemente din cei 2 vectori : B1 si B2 nr_b2 = 0; //daca vectorul B are elemente if(B[0] != 0) { for(i=1; i<= B[0]; i++) { //se face trierea fata de pivot if( B[i] <= pivot) { nr_b1++; B1[nr_b1] = B[i]; } else { nr_b2++; B2[nr_b2] = B[i]; } } }
134
//pe prima pozitie a vectorilor se memoreaza nr lor de //elemente B1[0] = nr_b1; B2[0] = nr_b2; } /********* media aritmetica a valorilor unui vector *****/ int Medie(int X[N+1]) { int S = 0; int i; //daca vectorul are elemente if(X[0] > 0) { for(i=1; i<=X[0]; i++) S = S + X[i]; S = S / X[0]; } else S = 0; return S; } /**************** Quick Sort secvential *****************/ // va fi folosit pentru sortarea elementelor finale din //fiecare nod int partition_seq( int a[N+1], int low, int high ) { int left, right; int aux; int pivot_item; pivot_item = a[low]; left = low; right = high; while ( left < right ) { // Muta stanga while item < pivot while( a[left] <= pivot_item ) left++; // Muta dreapta while item > pivot while( a[right] > pivot_item ) right--; if ( left < right ) //SWAP(a,left,right); { aux = a[left];
135
} // right este poz finala pt pivot a[low] = a[right]; a[right] = pivot_item; return right; } void quicksort_seq( int a[N+1], int low, int high ) { int pivot; if ( high > low ) { pivot = partition_seq( a, low, high ); quicksort_seq( a, low, pivot-1 ); quicksort_seq( a, pivot+1, high ); } } /********************************************************/ main(int argc, char **argv) { int B[N+1], B1[N+1], B2[N+1], C[N+1]; //N+1 elemente deoarece pe prima pozitie se memoreaza nr //de elemente //vectorul original de valori int V[N]={2, 7, 5, 4, 11, 10, 16, 3, 18, 20, 21, 16, 10, 5, 6, 921, 16, 32, 0, 13}; int nod_decizie, nr_transmisii, medie_val; int myrank, size, p, aux, pivot; double d; MPI_Status status; int i, j, k, l, msg_partener;
//initializari MPI MPI_Init (&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &myrank); MPI_Comm_size(MPI_COMM_WORLD, &size); //initializarea vectorilor for(i=1; i<N+1; i++) { //toti vectorii au -1 ca semn al lipsei datii valide B[i] = -1; B1[i] = -1; B2[i] = -1;
136
C[i] = -1; } //distribuirea elementelor in noduri se face in mod egal; //daca raman elemente, acestea vor fi preluate de ultimul //nod if(myrank != size-1) { j = 0; for(i=0; i< N/size; i++) { B[i+1] = V[myrank * (N/size) + i]; j++; } B[0] = j; } else { j = 0; for(i=0; i< N/size; i++) { B[i+1] = V[myrank * (N/size) + i]; j++; } for(i=0; i< N%size; i++) { B[N/size+i+1] = V[(myrank+1)* (N/size)+ i]; j++; } B[0] = j; } if(myrank == 0 ) { printf("***** QuickSort pe hypercube *****\n"); printf("Inainte de inceperea algoritmului:\n"); } MPI_Barrier(MPI_COMM_WORLD); printf("P %d la inceput - %d elemente: ", myrank, B[0]); for(j=1; j<= B[0]; j++) printf("%d ", B[j]); printf("\n"); //d este log2(size) d = log(size) / log(2); for(i=d; i>=1; i--)
137
{ MPI_Barrier(MPI_COMM_WORLD); //faza de calculare a pivotului //nodul la care se iau deciziile privitoare la //pivot; aceste nod se modifica functie de pasul //comunicarii nod_decizie = myrank | ((int)(pow(2, i))-1); medie_val = Medie(B); //nr de noduri ce tb sa comunice cu nodul-decizie nr_transmisii = (int)(pow(2, i)); //toate nodurile transmit cu nodul-decizie //corespunzator MPI_Send(&medie_val, 1, MPI_INT, nod_decizie, 99, MPI_COMM_WORLD); //nodul decizie asteapta datele if(myrank == nod_decizie) { //se calculeaza pivotul pivot = 0; for(j=0; j< nr_transmisii; j++) { MPI_Recv(&aux, 1, MPI_INT, myrank-j, 99, MPI_COMM_WORLD, &status); //se adauga media la suma deja existenta pivot = pivot+aux; } //se face media mediilor si se determina //pivotul pivot = pivot / nr_transmisii; } //pivotul calculat este distribuit if(myrank == nod_decizie) { for(j=0; j< nr_transmisii; j++) MPI_Send(&pivot, 1, MPI_INT, myrank-j, 99, MPI_COMM_WORLD); } //toate procesele primesc decizia de la nodurile de //decizie MPI_Recv(&pivot, 1, MPI_INT, nod_decizie, 99, MPI_COMM_WORLD, &status); //si realizeaza partitionarea corespunzator Partition(B, B1, B2, pivot); MPI_Barrier(MPI_COMM_WORLD);
138
//procesul de comunicare in cadrul sortarii //determinarea partenerului de comunicatie msg_partener = myrank ^ (int)(pow(2, i-1)); //daca bitul i este 0 if(( myrank & ((int)(pow(2, i-1))) )== 0) { //se trimite B2 si se primeste C MPI_Send(&B2, N+1, MPI_INT, msg_partener, 99, MPI_COMM_WORLD); MPI_Recv(&C, N+1, MPI_INT, msg_partener, 99, MPI_COMM_WORLD, &status); //reuniunea multimilor B1 si C k = 1; l = 1; //daca exista elemente in vectorii primiti, //ele se trec in vectorul final B if(B1[0] > 0) { for(k=1; k<= B1[0]; k++) B[k] = B1[k]; } if(C[0] > 0) { for(l=1; l<= C[0]; l++) B[k+l-1] = C[l]; } //actualizarea nr de elem al lui B B[0] = k+l-2; } else//daca bitul i nu e 0 { //se trimite B1 si se primeste C MPI_Send(&B1, N+1, MPI_INT, msg_partener, 99, MPI_COMM_WORLD); MPI_Recv(&C, N+1, MPI_INT, msg_partener, 99, MPI_COMM_WORLD, &status); //reuniunea multimilor B2 si C k = 1; l = 1; //daca exista elemente in vectorii primiti, //ele se trec in vectorul final B if(B2[0] > 0) { for(k=1; k<= B2[0]; k++) B[k] = B2[k]; } if(C[0] > 0) { for(l=1; l<= C[0]; l++)
139
B[k+l-1] = C[l]; } //actualizarea nr de elem al lui B B[0] = k+l-2; } MPI_Barrier(MPI_COMM_WORLD); } //la final, nodurile care au cel putin 1 elem fac sortarea //secventiala if(B[0]>0) quicksort_seq(B, 1, B[0]); //verificare la final if(myrank == 0 ) printf("\nLa finalul algoritmului:\n"); MPI_Barrier(MPI_COMM_WORLD); printf("P %d la final - %d elemente: ", myrank, B[0]); if(B[0] > 0) { for(j=1; j<= B[0]; j++) printf("%d ", B[j]); printf("\n"); } else printf("\n"); MPI_Finalize(); }
140
/**** Transpunerea unei matrici pe arhitecturi plasa ****/ /* Compilare : mpicc -o xxx xxx.c -lm Executie : mpirun -np 4 xxx Observatii: - Numarul de procese(noduri) va trebui sa fie p = (N/Q)^2, unde N este nr de elem de pe fiecare linie si coloana a matricii initiale, iar Q este dimensiunea matricilor ce revin fiecarui proces - Pentru rularea exemplului pt matrice 8x8 pe o plasa de 16 procesoare, trebuie sa avem N=8 si Q=2 -> 16 procese */ #include #include #include #include <stdio.h> <string.h> <math.h> "mpi.h" 8 4 //nr de procese va tb sa fie p = (N/Q)^2
#define N #define Q
/*********** algoritmul de mutare ********************/ /*(x1, y1) - colt stanga sus al matricii in care se produce mutarea (x2, y2) - colt dreapta jos al matricii in care se produce mutarea Matricea are 4 cadrane egale ca dimensiune II | I ----|---III | IV */ void Muta(int x1, int y1, int x2, int y2, int pas, int myrank, int L[Q][Q]) { int i, j, k; int send[Q][Q], recv[Q][Q]; int dim_latura; int rand, col, src, dest; MPI_Status status; dim_latura = N/Q; for(k=0; k<Q; k++) for(j=0; j<Q; j++) recv[k][j] = L[k][j]; //mutarile pe verticala - cadrane : I -> IV; III -> II for(i=0; i< pas/2; i++) //pas reprezinta nr de mutari ce //tb facute {
141
//se pregateste matricea de trimis for(k=0; k<Q; k++) for(j=0; j<Q; j++) send[k][j] = recv[k][j]; //asigurare ca toate procesele au actualizate //matricile MPI_Barrier(MPI_COMM_WORLD); //mutarea in jos : cadranul I -> IV //pentru fiecare rand ce tb considerat for(rand = x1+i; rand< x1+i+pas/2; rand++) { //pe coloanele din cadranele drepte for(col = y1+pas/2; col<=y2; col++) { //nodul care trimite if( (myrank/ dim_latura == rand) && (myrank % dim_latura == col) ) { dest = myrank + dim_latura; MPI_Send(&send, Q*Q, MPI_INT, dest, 99, MPI_COMM_WORLD); } //nodul care primeste - pe un rand mai //jos if( (myrank/ dim_latura == rand+1) && (myrank % dim_latura == col) ) { src = myrank - dim_latura; MPI_Recv(recv, Q*Q, MPI_INT, src, 99, MPI_COMM_WORLD, &status); } } } //mutarea in sus : cadranul III -> II //pentru fiecare rand ce tb considerat for(rand = x2-i; rand> x2-i-pas/2; rand--) { //pe coloanele din cadranele drepte for(col = y1; col<y1+pas/2; col++) { //nodul care trimite if( (myrank/ dim_latura == rand) && (myrank % dim_latura == col) ) { dest = myrank - dim_latura; MPI_Send(&send, Q*Q, MPI_INT, dest, 99, MPI_COMM_WORLD);
142
} //se asteapta terminarea unui rand de transmiteri MPI_Barrier(MPI_COMM_WORLD); } MPI_Barrier(MPI_COMM_WORLD); //mutarile pe orizontala - cadrane : II -> I; IV -> III for(i=0; i< pas/2; i++) { //se pregateste matricea de trimis for(k=0; k<Q; k++) for(j=0; j<Q; j++) send[k][j] = recv[k][j]; //asigurare ca toate procesele au actualizate //matricile MPI_Barrier(MPI_COMM_WORLD); //mutarea in dreapta : cadranul II -> I //pentru randurile din cadranele superioare for(rand = x1; rand< x1+pas/2; rand++) { //pe coloanele ce tb considerate for(col = y1+i; col<y1+i+pas/2; col++) { //nodul care trimite if( (myrank/ dim_latura == rand) && (myrank % dim_latura == col) ) { dest = myrank + 1; MPI_Send(&send, Q*Q, MPI_INT, dest, 99, MPI_COMM_WORLD); } //nodul care primeste o coloana la //dreapta
} //nodul care primeste - pe un rand //sus if( (myrank/ dim_latura == rand-1) (myrank % dim_latura == col) { src = myrank + dim_latura; MPI_Recv(recv, Q*Q, MPI_INT, 99, MPI_COMM_WORLD, &status); } }
143
if( (myrank/ dim_latura == rand) && (myrank % dim_latura == col+1) ) { src = myrank - 1; MPI_Recv(recv, Q*Q, MPI_INT, src, 99, MPI_COMM_WORLD, &status); } }
//mutarea in stanga : cadranul IV -> III //pe randurile din cadranele inferioare for(rand = (x2-pas/2)+1; rand<=x2; rand++) { //pe coloanele ce tb considerate for(col = y2-i; col> y2-i-pas/2; col--) { //nodul care trimite if( (myrank/ dim_latura == rand) && (myrank % dim_latura == col) ) { dest = myrank - 1; MPI_Send(&send, Q*Q, MPI_INT, dest, 99, MPI_COMM_WORLD); } //nodul care primeste - o coloana la //stanga if( (myrank/ dim_latura == rand) && (myrank % dim_latura == col-1) ) { src = myrank + 1; MPI_Recv(recv, Q*Q, MPI_INT, src, 99, MPI_COMM_WORLD, &status); } } } //se asteapta terminarea unui rand de transmiteri MPI_Barrier(MPI_COMM_WORLD); } //matricile din cadranele I si IV salveaza ceea ce au //primit if( (myrank / dim_latura >= x1) && (myrank / dim_latura < x1+pas/2) && (myrank % dim_latura >=y1+pas/2) && (myrank % dim_latura <= y2)) { for(k=0; k<Q; k++)
144
if( (myrank / dim_latura >= x1+pas/2) && (myrank / dim_latura <= x2) && (myrank % dim_latura >=y1) && (myrank % dim_latura < y1+pas/2)) { for(k=0; k<Q; k++) for(j=0; j<Q; j++) L[k][j] = recv[k][j]; } } /********************************************************/ main(int argc, char **argv) { int myrank, size; MPI_Status status; int A[N][N]; int L[Q][Q], aux[Q][Q]; //L reprezinta matricea locala fiecarui nod int dest, src; int rand, col; int dim_latura; //cate noduri sunt pe fiecare latura a //plasei int nr_mutari; int pas; int i, j, k, a; //initializarea matricii originale k = 0; for(i=0; i<N; i++) for(j=0; j<N; j++) A[i][j] = k++; //initializari MPI MPI_Init (&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &myrank); MPI_Comm_size(MPI_COMM_WORLD, &size); //procesul 0 afiseaza matricea initala if(myrank == 0) { printf("\n***** Transpusa unei matrici pe o arhitectura plasa *****\n\n"); printf("Matricea initiala:\n");
145
for(i=0; i<N; i++) { for(j=0; j<N; j++) printf("%d ", A[i][j]); printf("\n"); } } MPI_Barrier(MPI_COMM_WORLD); //fiecare proces isi construieste partea din matrice pe //care o vede for(i=0; i< Q; i++) for(j=0; j<Q; j++) L[i][j]=A[i+Q*(myrank/(N/Q))] [j+Q*(myrank%(N/Q))]; //cate noduri sunt pe fiecare latura a plasei dim_latura = N/Q; MPI_Barrier(MPI_COMM_WORLD); //Etapa I nr_mutari = dim_latura / 2; //pentru fiecare pas de divizare for(pas=N/Q; pas>=2; pas=pas/2) //se parcurg toate variantele de submatrici for(i=0; i< N/Q; i=i+pas) for(j=0; j<N/Q; j=j+pas) Muta(i, j, i+pas-1, j+pas-1, pas, myrank, L); //ultima transpunere(a elementelor lui L) e facuta de //fiecare nod in parte for(i=0; i<Q; i++) for(j=i+1; j<Q; j++) { a = L[i][j]; L[i][j] = L[j][i]; L[j][i] = a; } //realizarea matricii transpuse //procesul 0 poate actualiza deja matricea finala if(myrank == 0) { for(i=0; i<Q; i++) for(j=0; j<Q; j++)
146
A[i][j] = L[i][j];
//toate procesele trimit catre procesul 0 matricile locale if(myrank != 0) MPI_Send(&L, Q*Q, MPI_INT, 0, 99,MPI_COMM_WORLD); else { for(i=1; i<size; i++) { MPI_Recv(aux, Q*Q, MPI_INT, i, 99, MPI_COMM_WORLD, &status); //matricea primita este incadrata in matricea //finala for(j=0; j< Q; j++) for(k=0; k<Q; k++) A[j+(i/(N/Q))*Q][k+(i%(N/Q))*Q] = aux[j][k]; } } //procesul 0 afiseaza matricea finala if(myrank == 0) { printf("\n\nMatricea finala:\n"); for(i=0; i<N; i++) { for(j=0; j<N; j++) printf("%d ", A[i][j]); printf("\n"); } printf("\n\n"); } MPI_Finalize(); }
147
/*Algoritmul lui Cannon pentru inmultirea a doua matrici*/ /* Compilare : mpicc -o xxx xxx.c Executie : mpirun -np 4 xxx Observatii: - Numarul de procese va trebui sa fie p = (N/Q)^2, unde N este numarul de elemente de pe fiecare linie si coloana a matricii initiale, iar Q este dimensiunea matricilor ce revin fiecarui proces - Pentru rularea exemplului pt matrice 8x8 pe o plasa de 16 procesoare, trebuie sa avem N=8 si Q=2 -> 16procese */ #include #include #include #include <stdio.h> <string.h> <math.h> "mpi.h" 4 2 //nr de procese va tb sa fie p = (N/Q)^2
#define N #define Q
main(int argc, char **argv) { int myrank, size; MPI_Status status; MPI_Request req; int A[N][N], B[N][N], C[N][N]; int Alocal[Q][Q], Blocal[Q][Q], Clocal[Q][Q], Aaux[Q][Q], Baux[Q][Q], Caux[Q][Q]; int destA, srcA, destB, srcB; int rand, col, dist; int dim_latura; //cate noduri sunt pe fiecare latura a //plasei int nr_mutari; int pas; int i, j, k, l, a; //initializarea matricilor originale cu A == B k = 0; for(i=0; i<N; i++) for(j=0; j<N; j++) { A[i][j] = k++; B[i][j] = k-1; } //initializari MPI MPI_Init (&argc, &argv);
148
MPI_Comm_rank(MPI_COMM_WORLD, &myrank); MPI_Comm_size(MPI_COMM_WORLD, &size); //procesul 0 afiseaza matricile initiale if(myrank == 0) { printf("\n***** Algoritmul lui Cannon *****\n\n"); printf("Matricea initiala A:\n"); for(i=0; i<N; i++) { for(j=0; j<N; j++) printf("%d ", A[i][j]); printf("\n"); } printf("\n\n"); printf("\n\nMatricea initiala B:\n"); for(i=0; i<N; i++) { for(j=0; j<N; j++) printf("%d ", B[i][j]); printf("\n"); } printf("\n\n"); } MPI_Barrier(MPI_COMM_WORLD); //fiecare nod isi construieste partea din matrice pe care //o vede for(i=0; i< Q; i++) for(j=0; j<Q; j++) { Alocal[i][j] = A[i+Q*(myrank/(N/Q))][j+Q*(myrank%(N/Q))]; Blocal[i][j] = B[i+Q*(myrank/(N/Q))][j+Q*(myrank%(N/Q))]; } //cate noduri sunt pe fiecare latura a plasei dim_latura = N/Q; MPI_Barrier(MPI_COMM_WORLD); //Etapa I - alinierea initiala //pe orizontala pentru A-uri for(rand=1; rand< dim_latura; rand++) { pas = dim_latura - rand;
149
//daca este linia corespunzatoare if (myrank / dim_latura == rand) { //se calculeaza partenerii de comunicatie dist = (myrank+pas)% dim_latura; destA = rand * dim_latura + dist; dist = (myrank+ ( dim_latura - pas) ) % dim_latura; srcA = rand* dim_latura + dist; MPI_Send(&Alocal, Q*Q, MPI_INT, destA, 99,MPI_COMM_WORLD); MPI_Recv(&Aaux, Q*Q, MPI_INT, srcA, 99, MPI_COMM_WORLD, &status); //actualizarea informatiilor primite for(k=0; k< Q; k++) for(l=0; l<Q; l++) Alocal[k][l] = Aaux[k][l]; }
MPI_Barrier(MPI_COMM_WORLD); //Etapa I - alinierea initiala //pe verticala pentru B-uri for(col=1; col< dim_latura; col++) { pas = dim_latura - col; //daca este coloana corespunzatoare if (myrank % dim_latura == col) { //se calculeaza partenerii de comunicatie destB = (myrank+pas*dim_latura) % size; srcB = (myrank +(dim_latura-pas)*dim_latura) % size; MPI_Send(&Blocal, Q*Q, MPI_INT, destB, 99,MPI_COMM_WORLD); MPI_Recv(&Baux, Q*Q, MPI_INT, srcB, 99, MPI_COMM_WORLD, &status); //actualizarea informatiilor primite for(k=0; k< Q; k++) for(l=0; l<Q; l++) Blocal[k][l] = Baux[k][l]; }
} MPI_Barrier(MPI_COMM_WORLD);
150
//se pregatesc matricile rezultat locale pt inmultire for(l=0; l< Q; l++) for(j=0; j<Q; j++) Clocal[l][j] = 0; //Etapa II - deplasarea blocurilor si efectuarea //inmultirilor //interschimbarile blocurilor for(i=1; i<dim_latura; i++) { //intai se efectueaza produsul matricilor locale for(l=0; l< Q; l++) for(j=0; j<Q; j++) for(k=0; k<Q; k++) Clocal[l][j] += Alocal[l][k] * Blocal[k][j]; //apoi se deplaseaza matricile locale A si B rand = myrank / dim_latura; col = myrank % dim_latura; //se stabilesc partenerii de comunicatie destA = rand * dim_latura + (col + dim_latura - 1 ) %dim_latura; srcA = rand * dim_latura + (col + dim_latura + 1 ) %dim_latura; destB = (myrank + (dim_latura-1) * dim_latura ) % size; srcB = (myrank + dim_latura ) % size; MPI_Send(&Alocal, Q*Q, MPI_INT, destA, 99, MPI_COMM_WORLD); MPI_Send(&Blocal, Q*Q, MPI_INT, destB, 99, MPI_COMM_WORLD); MPI_Recv(&Aaux, Q*Q, MPI_INT, srcA, 99, MPI_COMM_WORLD, &status); MPI_Recv(&Baux, Q*Q, MPI_INT, srcB, 99, MPI_COMM_WORLD, &status); //fiecare nod isi reactualizeaza informatiile for(k=0; k< Q; k++) for(l=0; l<Q; l++) { Blocal[k][l] = Baux[k][l]; Alocal[k][l] = Aaux[k][l]; } MPI_Barrier(MPI_COMM_WORLD); }
151
//Etapa II - ultima inmultire locala - cea de dupa ultima //deplasare for(i=0; i< Q; i++) for(j=0; j<Q; j++) for(k=0; k<Q; k++) Clocal[i][j] += Alocal[i][k] * Blocal[k][j]; //Etapa III - procesul root primeste rezultatele if(myrank == 0) { //root-ul actualizeaza imediat matricea finala for(j=0; j<Q; j++) for(k=0; k<Q; k++) C[j][k] = Clocal[j][k]; //se primesc de la celelalte noduri sub-matricile si //se plaseaza datele in matricea rezultat finala for(i=1; i<size; i++) { MPI_Recv(&Caux, Q*Q, MPI_INT, i, 99, MPI_COMM_WORLD, &status); rand = i / dim_latura; col = i % dim_latura; for(j=0; j<Q; j++) for(k=0; k<Q; k++) C[rand*Q+j][col*Q+k] = Caux[j][k]; } } //celelalte procese doar trimit else MPI_Send(&Clocal, Q*Q, MPI_INT, 0,99,MPI_COMM_WORLD); //afisare la final if(myrank == 0) { printf("\n\nMatricea finala prin metoda Cannon:\n"); for(i=0; i<N; i++) { for(j=0; j<N; j++) printf("%d ", C[i][j]); printf("\n"); } printf("\n\n"); } //verificare prin metoda clasica for(i=0; i< N; i++) for(j=0; j<N; j++) { C[i][j] = 0;
152
for(k=0; k<N; k++) C[i][j] += A[i][k] * B[k][j]; } if(myrank == 0) { printf("\n\nMatricea finala prin metoda clasica:\n"); for(i=0; i<N; i++) { for(j=0; j<N; j++) printf("%d ", C[i][j]); printf("\n"); } printf("\n\n"); } MPI_Finalize(); }
153
/*Rezolvarea unui sistem de ecuatii prin metoda eliminarii gaussiene*/ /* Compilare : mpicc -o xxx xxx.c Executie : mpirun -np 3 xxx Observatii: - Dimensiunea sistemului (numarul de necunoscute) trebuie sa fie egal cu numarul de procese(sistemul trebuie sa fie compatibil determinat). In program este rezolvat un sistem de grad 3: x1 + x2 + x3 = 6 -x1 + 2*x2 x3 = 0 3*x1 + 3*x2 - 3*x3 = 0 */ #include #include #include #include <stdio.h> <string.h> <math.h> "mpi.h" 3 //acesta va tb sa fie si nr de procese
#define N
/************ algoritmul eliminarii gaussiene **********/ void GAUSSIAN_ELIMINATION(double A[N][N],double b[N],double y[N], int myrank) { int k, i, j; MPI_Status status; for(k=0; k<N; k++) { MPI_Barrier(MPI_COMM_WORLD); //procesul de rank k isi calculeaza propria linie //din matricea A if(myrank == k) { for(j = k+1; j<N; j++) //pasul de divizare A[k][j] = A[k][j] / A[k][k]; y[k] = b[k] / A[k][k]; A[k][k] = 1; } //odata calculat, y este distribuit MPI_Gather(&y[myrank], 1, MPI_DOUBLE, y, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD); MPI_Bcast(y, N, MPI_DOUBLE, 0, MPI_COMM_WORLD); for (i = k+1; i<N; i++)
154
{ // fiecare proces in paralel face rectificarea //conform pivotului if(myrank == i) { for (j = k +1; j<N; j++) //pasul de eliminare A[i][j] = A[i][j] - A[i][k] * A[k][j]; b[i] = b[i] - A[i][k] * y[k]; A[i][k] = 0; MPI_Send(&b[i], 1, MPI_DOUBLE, 0, 99, MPI_COMM_WORLD); } //procesul 0(root) actualizeaza matricea b//urilor if(myrank == 0) MPI_Recv(&b[i], 1, MPI_DOUBLE, i, 99, MPI_COMM_WORLD, &status); } //actualizarile liniilor din matricea A sunt //colectate de root MPI_Gather(&A[myrank], N, MPI_DOUBLE, A, N, MPI_DOUBLE, 0, MPI_COMM_WORLD); MPI_Bcast(A, N*N, MPI_DOUBLE, 0, MPI_COMM_WORLD); } }
/************ rezolvarea unui sistem triunghiular *******/ /* -acest proces tb sa se execute secvential doar de un singur nod -paralelizarea este imposibila dat dependentelor fata de calculele precedente -pe baza lui y si a matricii A sup triunghiulare se calculeaza y final - ac va fi solutia sistemului */ void RezSistTri(double A[N][N], double y[N], int myrank) { int i, j, k; double aux; if(myrank == 0) for(k= N-2; k>=0; k--) { aux = 0; for(i=k+1; i<N; i++) aux += A[k][i] * y[i]; y[k] = y[k] - aux;
155
/********************************************************/ main(int argc, char **argv) { int myrank, size; MPI_Status status; double A[N][N] = { {1, 1, 1}, {-1,2,-1}, {3, 3,-3} }; double b[N] = {6, 0, 0}; double y[N] = {0, 0, 0}; int i, j, k, a; //initializari MPI MPI_Init (&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &myrank); MPI_Comm_size(MPI_COMM_WORLD, &size); //procesul 0 afiseaza matricea initala if(myrank == 0) { printf("\n***** Rezolvarea unui sistem de ecuatii prin metoda eliminarii gaussiene *****\n\n"); printf("Matricea initiala:\n"); for(i=0; i<N; i++) { for(j=0; j<N; j++) printf("%f ", A[i][j]); printf("\n"); } printf("\nMatricea b initiala:\n"); for(i=0; i<N; i++) printf("%f ", b[i]); printf("\n\n"); } MPI_Barrier(MPI_COMM_WORLD); //triangularizarea sistemului GAUSSIAN_ELIMINATION(A, b, y, myrank); //rezolvarea sistemului RezSistTri(A, y, myrank);
156
//procesul 0 afiseaza matricea finala if(myrank == 0) { printf("\nMatricea A finala:\n"); for(i=0; i<N; i++) { for(j=0; j<N; j++) printf("%f ", A[i][j]); printf("\n"); } printf("\n\nMatricea b finala:\n"); for(i=0; i<N; i++) printf("%f ", b[i]); printf("\n\nMatricea y finala:\n"); for(i=0; i<N; i++) printf("%f ", y[i]); printf("\n\n"); } MPI_Finalize(); }
157
/********** Problema drumurilor minime cu aceeasi sursa (implementare sistolica)***********/ /* Compilare : mpicc -o xxx xxx.c Executie : mpirun -np 15 xxx Observatii: - Numarul de noduri al retelei este 15 = numarul de procese. Nodul 14 este cel in care se aduna informatiile - Nr noduri retea = Noduri_graf * (Noduri_graf + 1) / 2 - -1 = infinit, -2 = invalid(data lipsa) */ #include <stdio.h> #include "mpi.h" #define N 5 //nr de noduri al grafului -> 15 procese
main(int argc, char **argv) { int myrank, size; MPI_Status status; MPI_Request req; //C - matricea cost : //1 2 3 4 5 int C[N][N] = { { 0, -1, -1, -1, 0},//1 { 3, 0, 2, 25, -1}, //2 { 10, -1, 0, 6, -1},//3 { 15, -1, -1, 0,-1},//4 { -1, -1, 4, 5, -1},//5 }; int Cmax5[N]; int Cext[5][18] = { { 0, -1, -1, -1, 0, -1, -1, -1, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2 },//1 { -2, 3, 0, 2, 25, -1, 0, -1, -1, -2, -2, -2, -2, -2, -2, -2, -2, -2 },//2 { -2, -2, 10, -1, 0, 6, -1, -1, 0, -1, -2, -2, -2, -2, -2, -2, -2, -2 },//3 { -2, -2, -2, 15, -1, -1, 0, -1, -1, -1, 0, -2, -2, -2, -2, -2, -2, -2 },//4 { -2, -2, -2, -2, -1, -1, 4, 5, -1, -1, -1, -1, -2, -2, -2, -2, -2, -2 },//5 }; int dest_drt, dest_jos, src_sus, src_stg; int tip_nod; int sz; int i, j, k, l, a, init;
158
int ain, aout, bin, bout, r; //initializari MPI MPI_Init (&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &myrank); MPI_Comm_size(MPI_COMM_WORLD, &size); if(myrank == 0) printf("\n***** Problema algebrica a drumurilor pentru drumuri cu aceiasi sursa(implementare sistolica) *****\n\n"); //setare vecini: -1==vecin lipsa switch(myrank) { case 0: dest_drt = myrank+1; dest_jos = -1; src_sus = -1; src_stg = -1; break; case 1: dest_drt = myrank+1; dest_jos = myrank+4; src_sus = -1; src_stg = myrank-1; break; case 2: dest_drt = myrank+1; dest_jos = myrank+4; src_sus = -1; src_stg = myrank-1; break; case 3: dest_drt = myrank+1; dest_jos = myrank+4; src_sus = -1; src_stg = myrank-1; break; case 4: dest_drt = -1; dest_jos = myrank+4; src_sus = -1; src_stg =myrank-1; break; case 5:dest_drt = myrank+1; dest_jos = -1; src_sus = myrank-4; src_stg = -1; break; case 6: dest_drt = myrank+1; dest_jos = myrank+3; src_sus = myrank-4; src_stg = myrank-1; break; case 7: dest_drt = myrank+1; dest_jos = myrank+3; src_sus = myrank-4; src_stg = myrank-1; break; case 8: dest_drt = -1; dest_jos = myrank+3; src_sus = myrank-4; src_stg = myrank-1; break; case 9:dest_drt = myrank+1; dest_jos = -1; src_sus = myrank-3; src_stg = -1; break; case 10: dest_drt = myrank+1; dest_jos = myrank+2; src_sus = myrank-3; src_stg = myrank-1; break; case 11: dest_drt = -1; dest_jos = myrank+2; src_sus = myrank-3; src_stg = myrank-1; break;
159
case 12: dest_drt = myrank+1; dest_jos = -1; src_sus = myrank-2; src_stg = -1; break; case 13: dest_drt = -1; dest_jos = myrank+1; src_sus = myrank-2; src_stg = myrank-1; break; //nodul 14 are regim special - el aduna informatiile case 14:dest_drt = -1; dest_jos = -1; src_sus = myrank-1; src_stg = -1; break; } init = 1; ain = -2; bin = -2; aout = -2; bout= -2; r = -2; /*la fiecare pas - in fct de tipul sau, fiecare nod realizeaza alte actiuni Tipuri noduri: 1. Cele de pe prima diagonala(nu au vecin stang) 2. Restul */ for(i=0; i< 16; i++) { MPI_Barrier(MPI_COMM_WORLD); if(src_stg == -1)//noduri de pe diagonala stanga { aout = ain; if(dest_drt !=-1) MPI_Send(&aout, 1, MPI_INT, dest_drt, 99, MPI_COMM_WORLD); if(src_sus != -1) MPI_Recv(&ain, 1, MPI_INT, src_sus, 99, MPI_COMM_WORLD, &status); else ain = Cext[myrank][i]; //incepand cu pasul 13, proc 14 inregistreaza //valorile if((myrank == 14) && (i > 11)) Cmax5[i-12] = ain; } { if( ain == -2) //invalid { aout = ain; bout = bin;
else
160
//daca nu sunt noduri de pe coloana din //dreapta if(dest_drt != -1) MPI_Send(&bout, 1, MPI_INT, dest_drt, 99, MPI_COMM_WORLD); if(src_stg != -1) MPI_Recv(&bin, 1, MPI_INT, src_stg, 99, MPI_COMM_WORLD, &status); if(dest_jos != -1) MPI_Send(&aout, 1, MPI_INT, dest_jos, 99,MPI_COMM_WORLD); //daca nu sunt noduri de pe prima linie if(src_sus != -1) MPI_Recv(&ain, 1, MPI_INT, src_sus, 99, MPI_COMM_WORLD, &status); else ain = Cext[myrank][i]; } else//nodul lucreaza dupa ecuatiile din //algoritm { if(init == 1) { r = ain; init = 0; bout = bin; aout = -2; //nil //daca nu sunt noduri de pe //coloana din dreapta if(dest_drt != -1) MPI_Send(&bout, 1, MPI_INT, dest_drt, 99, MPI_COMM_WORLD); if(src_stg != -1) MPI_Recv(&bin, 1, MPI_INT, src_stg, 99, MPI_COMM_WORLD, &status); if(dest_jos != -1) MPI_Send(&aout, 1, MPI_INT, dest_jos, 99, MPI_COMM_WORLD); //daca nu sunt noduri de pe prima //linie if(src_sus != -1) MPI_Recv(&ain, 1, MPI_INT, src_sus, 99, MPI_COMM_WORLD, &status); else ain = Cext[myrank][i]; } { if(ain == -1) aout = r+bin;
else
161
} } //afisare dupa fiecare pas pentru verificare if(myrank == 0) printf("\nDupa pasul %d:\n", i); MPI_Barrier(MPI_COMM_WORLD); printf("P[%d]: ain=%d bin=%d aout=%d bout=%d r=%d\n", myrank, ain, bin, aout, bout, r); MPI_Barrier(MPI_COMM_WORLD); } //afisare finala if(myrank == 14) { printf("\n\nDrumurile maxime de la 5 la restul nodurilor sunt:\n"); for(i=0; i<N-1; i++) printf("5->%d: %d\n", i+1, Cmax5[i]); } MPI_Finalize(); }
if(r == -1) aout = ain; if(bin == -1) aout = ain; if((ain!=-1) && (bin!=-1) && (r!=-1) ) { if (ain < (r+bin)) aout = r+bin; else aout = ain; } bout = bin; //daca nu sunt noduri de pe //coloana din dreapta if(dest_drt != -1) MPI_Send(&bout, 1, MPI_INT, dest_drt, 99, MPI_COMM_WORLD); if(src_stg != -1) MPI_Recv(&bin, 1, MPI_INT, src_stg, 99, MPI_COMM_WORLD, &status); if(dest_jos != -1) MPI_Send(&aout, 1, MPI_INT, dest_jos, 99, MPI_COMM_WORLD); //daca nu sunt noduri de pe prima //linie if(src_sus != -1) MPI_Recv(&ain, 1, MPI_INT, src_sus, 99, MPI_COMM_WORLD, &status); else ain = Cext[myrank][i]; }
162
Bibliografie
1. A. Gibbons, W. Rytter. Efficient Parallel Algorithms, Cambridge University Press, 1988 2. V. Kumar, A. Grama A. Gupta &G Karypis. Introduction to Parallel Computing: Design andAnalysis of Algorithms, BenjaminCummings,1994 3. Y. Robert, D. Trystram. Parallel Implementation of the Algebraic Path Problem, LNCS 237, 1986, p.149-155 4. Murray Cole. Note de curs, Edinburgh University, 2002 5. M. Craus. Algoritmi pentru prelucrri paralele, Editura Gh.Asachi Iasi, 2002 6. William Gropp, Steven Huss-Lederman, Andrew Lumsdaine, Ewing Lusk, Bill Nitzberg, William Saphir, and Marc Snir. MPIThe Complete Reference: Volume 2, The MPI-2 Extensions. MIT Press, Cambridge, MA, 1998. 7. William Gropp, Ewing Lusk, and Rajeev Thakur. Using MPI-2: Advanced Features of the Message-Passing Interface. MIT Press, Cambridge, MA, 1999. 8. A UsersGuide to MPI, Peter S. Pacheco, Department of Mathematics, Universitz of San Francisco, California, 1995 9. Installation and Userss Guide to MPICH; D. Ashton, W. Gropp,E. Lusk; Argone National Laboratory 10. D. Grigora. Calculul paralel. De la sisteme la programarea aplicaiilor, Computer Libris Agora, 2000, 11. B. Wilkinson, M. Allen. Parallel Programming, Prentice Hall, 1999 12. Marc Snir, Steve W. Otto, Steven Huss-Lederman, David W. Walker, and Jack Dongarra. MPIThe Complete Reference: Volume 1, The MPI Core, 2nd edition. MIT Press, Cambridge, MA, 1998. 13. T. Cormen, C. Leiserson, R. Rivest. Introducere n algoritmi, Computer Libris Agora, 2000 14. H. Attiya, J. Welch. Distributed Computing: Fundamentals, Simulations and Advanced Topics, The McGraw-Hill Companies, London, 1998
163