Sunteți pe pagina 1din 163

Mitic Craus Cristian-Mihai Amarandei Bogdan Romanescu

ALGORITMI I LIMBAJE PENTRU CALCULUL PARALEL Indrumar de laborator

Iai 2005

Partea I : Algoritmi paraleli


Primitive ale calculului paralel.............................................................. 9 Comprimarea (Reducerea) ............................................................... 9 Calculul Prefixelor (Scan) ............................................................... 11 Dublarea (scurtcircuitarea) ............................................................ 13 Comunicri colective ............................................................................ 15 Difuzarea unu-la-toi ....................................................................... 15 Comunicarea personalizat unu-la-toi (scatter) .......................... 20 Difuzarea toi-la-toi ........................................................................ 22 Comunicarea personalizat toi-la-toi .......................................... 26 Sortarea paralel .................................................................................. 29 Sortarea paralel bazata pe metoda numrrii ............................ 29 Sortarea par-impar (Odd-Even-Sort) ............................................ 31 Sortarea prin interclasarea de secvene bitone ............................. 32 Sortarea rapid pe hipercub ........................................................... 37 Calcul matricial .................................................................................... 38 Transpunerea unei matrice............................................................. 38 Inmulirea a dou matrici ............................................................... 40 Sisteme de ecuatii.................................................................................. 44 Drumuri optime n grafuri .................................................................. 47 Comentarii bibliografice ................................................................. 53

Partea a-II-a : Introducere in MPI


Standardul MPI .................................................................................... 57 Modelul de execuie .............................................................................. 58 Tipuri de date n MPI .......................................................................... 59 Comunicarea ntre procese .................................................................. 60 Comunicri unu-la-unu blocante ................................................... 60 Comunicri unu-la-unu neblocante ............................................... 64 Comunicri colective ............................................................................ 69 Bariera .............................................................................................. 70 Difuzia (Broadcast) .......................................................................... 70 Comunicarea personalizat unu-la-toi (Scatter) ......................... 72 Comunicarea toi-la-unu - Colectarea datelor (Gather) .............. 73 Operaii de reducere ........................................................................ 76 Calculul prefixelor ........................................................................... 81 Corectitudinea operaiilor colective ............................................... 82 Tipuri de date definite de utilizator ............................................... 86 2

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

Partea a-III-a : Exemple de programe MPI 109


Bibliografie ..................................................................................... 163

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 .

PARTEA I ALGORITMI PARALELI Autor: Mitic Craus

Primitive ale calculului paralel


- Comprimarea (reducerea) - Calculul prefixelor (scan) - Dublarea (scurtcircuitarea)

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.

Algoritm recursiv de comprimare


proc comprim_rec (A,l,l+m-1) begin if (m < 2) then return A[l] else return comprim_rec(A,l,l+m/2-1) comprim_rec(A,l+m/2,l+m-1)) end

Algoritm iterativ de comprimare


Iniial cele n elemente a1, a2,...,an sunt memorate n locaiile A[n], A[n+1],...,A[2n-1] ale unui tablou de dimensiune 2n. proc sum(A,) 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]; end

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.

Reducerea numrului de procesoare


Se poate observa c n fiecare pas l, l = 0,...,m-1, sunt active [n/( 2l)] procesoare. Aceasta sugereaz faptul ca este posibil reducerea numrului de procesoare fr a afecta complexitatea algoritmului. Presupunnd ca p < n/2, se partiionez mulimea celor n elemente a1,...,an n p submulimi astfel: primele p-1 submultimi contin k = n/p elemente, iar ultima conine n-(p-1)n/p ( n/p) elemente. Fiecrei submulimi i se asociaz un procesor. Procesorul asociat unei submulimi S = {ai1,...,aik} rezolv problema determinrii valorii ai1...aik n n/p-1 uniti de timp. Acionnd n paralel, cele p procesoare reduc problema iniial la o problem similar de n dimensiune p, n /p-1 uniti de timp. In continuare, aplicnd comprimarea asupra problemei rezultate, se obine rezultatul final n logp uniti de timp. Rezult un timp total de 10

n/p-1+logp uniti de timp. Dac p = [n/ logn] atunci n/p-1+logp 2logn-1-loglogn O(logn).

Calculul Prefixelor (Scan)


Tehnica calculrii prefixelor dezvolt tehnica comprimrii recursive n care parcurgerea arborelui asociat execuiei se face dinspre frunze spre rdcin. Algoritmii de calcul a prefixelor parcurg arborele n ambele sensuri. Utiliznd notaiile anterioare, calculul prefixelor const n determinarea valorilor a1, a1a2, , a1...an unde este o operaie binar asociativ. Se presupune c datele de intrare sunt iniial memorate ntr-un tablou unidimensional A[0..2n-1] (A[n+i]=ai) iar n este de forma n = 2d.

Algoritmi de calcul al prefixelor


Operaia admite element simetric proc calcul_prefixe(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]; B[1]= A[1]; for k = 1 to m do for all j:2k j 2k+1-1 par do if bit0(j) = 1 then B[j]=B[[(j-1)/2]]; else B[j]=B[j/2](-A[j+1]); End

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])

Aplicaii ale operaiei scan la recurene


Fie recurena Xk = Xk-1A[k], k > 1, X1 = A[1] Dac este un operator binar asociativ atunci Xk= A[1] A[2] ... A[k] Astfel, recurene simple pot fi rezolvate cu algoritmi paraleli pentru calculul prefixelor.

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

Aplicaii ale scurtcircuitrii: Calcul ranguri


Un exemplu tipic de utilizare a tehnicii dublrii l constituie calcularea numerelor de ordine ale elementelor unei liste, fa de sfritul acesteia. Pentru un element k L, rang[k] va conine n final numrul i al elementor din lista L care se afl n list dupa elementul k, n ordinea dat de pointeri.

Vizualizare

Algoritm Calcul ranguri


proc calcul_ranguri(L,rang,+) begin /*Initializarea vectorilor p si rang*/ for all k:k L par do p[k]=succ(k); /*succ(k)=elementul care urmeaza lui k in lista L*/ if p[k] k then rang[k]=1 else rang[k]=0 /*Aplicarea tehnicii dublarii*/ scurtcircuitare(L,rang,+); end 14

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:

Difuzarea unu-la-toi pe un inel


Procesoarele sunt numerotate de la 0 la p. Procesorul 0 este emitentul mesajului M de lungime m, unde m este numrul componentelor atomice (cuvinte) ale mesajului. Fiecare pas de transmisie este ilustrat printr-o sgeat de la surs la destinaie, punctat i numerotat. Numrul semnific pasul n care este transmis mesajul. Sursa trimite mesajul celor 2 vecini ai si n pai diferii. Fiecare procesor primete un mesaj de la unul din vecinii si i l trimite mai departe la cellalt vecin. Procesul continu s fie activ pn cnd toate procesoarele au primit o copie a mesajului. Pentru o reea de tip inel cu p procesoare, difuzia va fi finalizat n [p/2] pai. Timpul de difuzie este dat de relaia: Tone_to_all =( ts+twm ) p/2

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.

Difuzarea unu-la-toi pe o plas toroidal


Considerm problema difuziei unu-la-toi pe o plasa toroidal cu p liniii si p coloane. Procesoarele sunt numerotate de la 0 la p. Procesorul 0 este emitentul mesajului. In prima faz de comunicare este executat o difuzie de la surs la cele ( p -1) procesoare de pe aceeai linie. Dup ce toate procesoarele de pe linie au primit mesajul, este iniiat o difuzie unu-la-toi pe coloana corespunztoare. La sfritul fazei a doua, fiecare procesor din plas conine o copie a mesajului iniial. Paii de comunicare sunt prezentai n figura de mai jos, unde procesorul 0 este sursa. Paii 1 i 2 corespund primei faze, iar paii 3 i 4 corespund celei de-a doua faze. Dac dimensiunea mesajului este m, difuzia unu-la-toi pe linie dureaz (ts+twm)[ p /2] (timpul necesar unei difuzii unu-la-toi pe un inel cu p procesoare). n a doua faz, difuzia unu-la-toi pe coloane se desfoar n paralel, iar timpul de transmisie este egal cu cel din prima faz. Timpul total de difuzie este: Tone_to_all =2( ts+twm ) [ p /2] Putem folosi o procedur similar pentru difuzia unu-la-toi pe o plas tridimensional. In acest caz, liniile de p1/3 procesoare care corespund la cele 3 dimensiuni ale plasei sunt tratate ca inele. Aplicnd procedura pentru inel n trei faze, cte una pentru fiecare dimensiune, timpul de difuzie unu-la-toi este: Tone_to_all =3( ts+twm ) [ p /2]

16

Difuzarea unu-la-toi pe hipercub


Figura de mai jos exemplific difuzia unu-la-toi pe un hipercub cu 8 procesoare, procesorul 0 fiind sursa mesajului.

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.

Algoritm de difuzare unu-la-toi pe un cub d-dimensional


In algoritmul prezentat mai jos se consider nodul 0 ca surs a mesajului M difuzat. Procedura se execut n paralel pe toate procesoarele. Fiecare procesor i cunoate propriul identificator my_id. 17

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 (scatter)


Un procesor trimite cte un mesaj Mp la fiecare procesor p.

Comunicarea personalizata unu-la-toi pe hipercub


Comunicarea unu-la-toi personalizat este diferit de difuzia unu-latoi deoarece procesorul surs trasmite p mesaje cte unul pentru fiecare procesor. Spre deosebire de difuzia unu-la-toi, acest tip de comunicare nu implic multiplicarea mesajelor. Operaia invers este comunicarea toi-la-unu (gather), n care un singur procesor colecteaz mesaje unice de la toate celelate procesoare. Operatia de gather este diferit de cea de acumulare deoarece nu implic operaii de combinare sau comprimare (reducere a datelor).

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.

Difuzarea toi-la-toi pe un inel


In difuzarea unu-la-toi pe inel, cel mult dou ci de comunicare sunt active la un moment dat. In cazul difuzrii toi-la-toi, toate canalele pot fi ocupate simultan deoarece ntotdeauna fiecare procesor va avea o informaie ce trebuie trimis vecinilor si. Fiecare procesor trimite nti mesajul su unuia dintre vecini. In fiecare pas ulterior, mesajul primit de la un vecin este trimis celuilalt vecin. Timpul necesar difuziei toi-la-toi pe un inel este: Tall-to-all=(ts+twm)(p-1)

22

Difuzarea toi-la-toi pe o plas bidimensional


Ca i n cazul difuzrii unu-la-toi, difuzarea toi-la-toi pe o plas bidimensional e bazat pe algoritmul pe inel, liniile i coloanele fiind tratate ca inele. Comunicarea se desfoar n 2 faze. In prima faz, pe fiecare linie din plas se execut o difuzare toi-la-toi utiliznd procedura pe inel. In aceast faz procesoarele colecteaz cele p mesaje de la cele p procesoare corespunztoare unei linii. Fiecare procesor grupeaz mesajele ntr-un singur mesaj. A doua faz reprezint o difuzare toi-la-toi pe coloane a mesajelor rezultate n urma gruprii. Timpul total necesar comunicarii este: Tall-to-all=2ts( p - 1)+twm(p - 1) 23

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

Difuzarea toi-la-toi pe hipercub


Procedura necesit logp pai. In fiecare pas, comunicarea se desfoar pe cte o dimensiune a hipercubului cu p procesoare. n fiecare pas, perechi de procesoare schimb mesaje i dubleaz dimensiunea mesajului ce va fi transmis n pasul urmtor concatennd mesajul su cu cel primit. Figura de mai jos prezint aceti pai pentru un hipercub cu 8 noduri, cu canale de comunicaie bidirecionale. Dimensiunea mesajului transmis n pasul i este 2(i-1)m. Timpul necesar unei perechi de procesoare s primeasc i s transmit mesaje unul celuilalt este ts+2(i-1)twm. Timpul necesar ntregii proceduri este: Tall-to-all=ts log p+twm(p - 1)

Algoritm de difuzare toi-la-toi pe un cub d-dimensional


proc ALL_TO_ALL_BC_HCUBE(my_id, my_msg, d, result) begin result = my msg; for i=0 to d-1 do partner = my_id XOR 2i; send result to partner; receive msg from partner; result = result U msg; end 25

Comunicarea personalizat toi-la-toi


Fiecare procesor s trimite cte un mesaj Msd la fiecare procesor d.

Comunicarea personalizat toi-la-toi pe un inel


Figura de mai jos descrie paii unei comunicri personalizate toi-la toi pe un inel de 6 procesoare. Pentru a efectua aceasta operaie, fiecare procesor trimite p-1 mesaje, fiecare de dimensiune m. In figur aceste mesaje sunt definite de perechile de ntregi (i, j), unde i reprezint sursa i j destinaia final a mesajului.

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.

Comunicarea personalizat toi-la-toi pe o plas bidimesional


In cazul comunicrii personalizate toi-la-toi pe o plas cu p procesoare ( p p ), fiecare procesor i grupeaz cele p mesaje n funcie de coloana procesorului destinaie. In figura de mai jos este prezentat o plas de 3x3 procesoare, n care fiecare procesor are iniial 9 mesaje de dimensiune m cte unul pentru fiecare procesor. Fiecare procesor grupeaz mesajele n 3 pachete de cte 3 mesaje fiecare (n general p pachete de cte p mesaje fiecare). Primul pachet conine mesajele destinate procesoarelor 0, 3, 6; al doilea pachet conine mesaje pentru procesoarele 1, 4, 7; ultimul pachet conine mesaje pentru procesoarele 2, 5, 8. Dupa ce mesajele au fost mpachetate, comunicarea personalizat toi-la-toi este executat independent pe fiecare linie, cu pachete de dimensiune m p . Un pachet conine mesajele pentru toate cele
p procesoare de pe o coloana.

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.

Comunicarea personalizat toi-la-toi pe un hipercub


Paii de comunicare necesari realizrii acestei operaii pe un hipercub cu p noduri (procesoare) sunt n numr de logp. In fiecare pas, comunicarea se desfoar pe cte o dimensiune a hipercubului i fiecare procesor conine p mesaje de dimensiune m. In timpul comunicrii pe o anumit dimensiune, procesorul trimite p/2 din aceste mesaje. Destinaiile acestor mesaje sunt procesoarele din cellalt subcub. Timpul total de comunicare este: Tall_to_all_pers = (2ts + twmp)log p

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.

Sortarea paralel bazata pe metoda numrrii


Alg. Muller si Preparata, 1975
proc sortare_prin_numarare () begin for all i,j: 1 i,j n par do if A[i] A[j] R[i+n-1,j]=1 else R[i+n-1,j]=0; for all j: 1 j n par do sum(R[j],+); P[j] = R[1,j]; for all j: 1 j n par do A[P[j]] =A[j]; end Tabloul A de dimensiune n conine iniial irul (ai)1 n. R este un tablou bidimensional de mrime (2n-1)xn, R[j] desemneaz coloana j a tabloului R. Poziia P[j] n care aj trebuie plasat este calculat astfel nct ai<aj dac i<j.

29

Exemplu: 2,6,3,8 i\j 1 2 3 4 5 6 7 1 1 1 0 1 0 0 0 2 3 1 2 1 1 1 0 2 1 1 1 0 1 0 3 4 4 2 2 1 1 1 1

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

Sortarea par-impar (Odd-Even-Sort)


Versiune a algoritmului BubbleSort pe un lant de procesoare procedure ODD-EVEN_PAR(n) begin id = eticheta procesorului for i=1to n do if i este impar then if id este impar then compare-exchange_min(id+1) ; else compare-exchange_max(id-1) ; if i este par then if id este par then compare-exchange_min(id+1) ; else compare-exchange_max(id-1) ; endfor end

Exemplu: Sortarea a 8 elemente cu algoritmul Par-Impar

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

Sortarea prin interclasarea de secvene bitone


Operaia de baz: sortarea unei secvene bitone. O secven biton este o secvent de elemente <a0, a1, , an-1> cu urmtoarele proprieti: 1. Exist i a.. <a0, ,ai> este monoton cresctoare i <ai+1, , an-1> este monoton descresctoare SAU 2. Exist o permutare circular a.. s fie satisfacut condiia anterioar.

Exemple de secvene bitone


<1, 2, 4, 7, 6, 0> : nti crete i apoi descrete, i = 3. <8, 9, 2, 1, 0, 4> : shift stnga cu 4 poziii, i = 3.

Proprieti ale secvenelor bitone


Fie s = <a0, a1, , an-1> o secven biton, s1=<min{a0, an/2}, min{a1, an/2 +1}min{an/2-1,an-1}> i s2=<max{a0, an/2}, max{a1,an/2+1}max{an/2-1,an-1}> In secvena s1 exist bi = min{ai, an/2+i} atfel nct toate elementele din faa lui bi sunt din secvena cresctoare i toate elementele de dup bi sunt din secvena descresctoare. Secvena s2 are i ea un punct similar. Secvenele s1 i s2 sunt bitone. Fiecare element din s1 este mai mic dect fiecare element din s2.

1. 2. 3. 4.

Esena sortrii unei secvene bitone


Problema sortrii unei secvene bitone de lungime n se reduce la sortarea a dou secvene bitone de dimensiune n/2.

32

Exemplu de sortare a unei secvene bitone

Comparatori pentru sortarea a doua numere

Reea de sortare R1 de secvene bitone (n=16)

33

Conversia unei secvene oarecare ntr-o secvena biton


Pentru a sorta o secven de n elemente, prin tehnica sortrii unei secvene bitone, trebuie s dispunem de o secven biton format din n elemente. 1. Dou elemente formeaz o secven biton. 2. Orice secven nesortat este o concatenare de secvene bitone de lungime 2. Ideea de sortare: Combinarea ntr-o secvena mai larg pn cnd se obine o secven biton de lungime n.

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.

Algoritm de sortare paralel bazat pe interclasarea de secvene bitone


Remarcabil pentru simplitatea sa este algoritmul lui Batcher, 1968, cunoscut sub numele de ,,bitonic-merge-sorting. Algoritmul utilizeaz paradigma combinrii recursive, uor modificat. Convenii: 1. A[0:n-1] este tabloul de intrare; 34

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

Algoritmul Batcher de sortare bitonic


proc sortare_bitonica(A[i:i+b-1],d) begin if (b = 2) (A[i],A[i+1]) = COMP_EX(A[i],A[i+1];d) else sortare _bitonica (A[i:i+b/2-1],0); sortare _bitonica (A[i+b/2:i+b-1],1); interclasare _bitonica (A[i:i+b-1],d); end

Algoritm de interclasare a dou secvene bitone


proc interclasare_bitonica(A[i:i+b-1],d) begin if b = 2 (A[i],A[i+1]) = COMP_EX(A[i],A[i+1];d) else for all j:0 j < b/2 par do (A[i+j],A[i+b/2+j]) = COMP_EX(A[i+j],A[i+b/2+j];d); interclasare _bitonica (A[i:i+b/2-1],d); interclasare _bitonica (A[i+b/2:i+b-1],d) end

Implementare pe o main CREW-PRAM


Interclasarea_bitonic necesit n procesoare i O(logn) timp, rezultnd pentru sortare_bitonica un timp de O(log2n) i un necesar de O(n) procesoare. Eficiena algoritmului este, evident, O(1/logn).

Implementare pe o reea de sortare


Reeaua (R2,R1) implementeaz algoritmul lui Batcher. Numrul de faze este logn. Fazele sunt compuse din O(logn) pai.

35

Implementarea algoritmului lui Batcher pe hipercub


Cubul binar multidimensional este o arhitectur ideal pentru implementarea procedurii sortare_bitonica. Sortarea bitonica const n d faze de combinare (merging): M0,M1,...,Md-1, unde Mi realizeaz combinarea perechilor de secvene de lungime 2i. Modelul algoritmic este paradigma combinrii recursive n varianta execuiei combinarii naintea celor dou apeluri recursive. Execuia fazei Mi pe hipercub necesit utilizarea succesiv a dimensiunilor Ei,Ei-1,...,E1,E0. Planificare dimensiunilor Planificarea utilizrii dimensiunilor pentru o sortare complet poate fi reprezentat prin urmatoarea paradigm: Faz Dimensiuni active M0 : E0 E1 E0 M1 : M2 : E2 E1 E0 ............ Ed-1 Ed-2 ... E1 E0 Md-1 : Planificarea dimensiunilor unui hipercub de ordinul 4

Planificarea dimensiunilor corespunde la fazele sortrii prin interclasarea de secvene bitone.

36

Sortarea rapid pe hipercub


S ne amintim c un hipercub cu d dimensiuni este format din dou hipercuburi cu d-1 dimensiuni. Ideea de sortare rapid pe hipercub este de a plasa cele dou secvene rezultate n urma operaiei de partiionare n jurul pivotului a secvenei de sortat memorat n nodurile hipercubului. Operaia se repet apoi recursiv pe subcuburi. Selectarea pivotului este problema cheie. Selectarea unui pivot care s partitioneze secvena de sortat n dou subsecvente de dimensiuni aproximativ egale este dificil.

Algoritmul de sortare rapida pe hipercub


proc sortare_rapida_pe_hipercub(B, n) begin id = eticheta procesorului; for i=1 to d do x = pivot; partiioneaz B n B1 i B2 a.. B1 x B2; if ith bit is 0 then trimite B2 procesorului vecin pe a dimensiunea i; C = subsecvena primit de la vecinul dimensiune i; B = B1 U C; else trimite B1 procesorului vecin pe a dimensiunea i; C = subsecvena primit de la vecinul dimensiune i; B = B2 U C; Aplic lui B sortarea rapid secvenial; end

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

Algoritmul secvenial standard:


proc MAT_TRANSP(A) begin for i = 0 to n-1 do for j = i+1 to n-1 do interchange(A[i,j],A[j,i]) ; end Transpunerea unei matrice 44 cu o plasa de 16 de procesoare

Paii de comunicare

Configuraia final

38

Transpunerea unei matrice 88 cu o plasa de 16 de procesoare

Paii de comunicare

Rearanjamente locale

Algoritm recursiv de transpunere a unei matrice 88 - Pasii I-II

Divizarea matricei n 4 blocuri Intreschimbarea blocurilor stnga-jos cu dreapta-sus

Divizarea fiecrui bloc n sub-blocuri n fiecare bloc: Intreschimbarea sub-blocurilor stnga-jos cu dreapta-sus

39

Algoritm recursiv de transpunere a unei matrice 88 - Pasii III-IV

Ultima divizare

Configuraia final

Implementare pe un hipercub cu p procesoare


Hipercubul este divizat n 4 subcuburi cu p/4 procesoare. Sferturile de matrice (quadranii) sunt mapate pe cele 4 subcuburi. In fiecare subcub se interschimb blocurile stnga-jos cu dreapta-sus. Se repet procedeul n fiecare subcub.

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.

Inmulirea a dou matrici


proc MAT_MULT (A,B,C) begin for i = 0 to n-1 do for j = 0 to n-1 do C[i,j] =0 ; for k =0 to n-1 do C[i,j] = C[i,j] +A[i,k]B[k,j]; end 40

Algoritmul nmulirii de blocuri


proc MAT_MULT (A,B,C) begin for i = 0 to q-1 do for j = 0 to q-1 do Ci,j = [0] ; for k =0 to q do Ci,j = Ci,j +Ai,kBk,j; end Matricile A si B sunt divizate n blocuri Ai,k i respectiv Bk,j , de dimensiuni (n/q)(n/q).

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.

Algoritmul lui Cannon


Algoritmul lui Cannon este asemntor cu cel corespunztor implementrii standard dar cu consum mai mic de memorie. Diferenta const n faptul c fiecare bloc este procesat la momente diferite n locuri diferite. Pentru aceasta, blocurile sunt deplasate ciclic. Iniial blocurile sunt aliniate prin deplasri ciclice la stnga (n A) i n sus (n B). Urmeaza p-1 pai de nmuliri locale i adunri locale de blocuri, urmate de deplasri ciclice la stnga (n A) i n sus (n B).

41

Alinierea iniial (prima faz)

A si B dupa alinierea iniial

Poziiile blocurilor dup prima deplasare

Poziiile blocurilor dup a II-a deplasare

Poziiile blocurilor dup a III-a deplasare

42

Complexitatea implementrii pe o plas de proecesoare


Timpul consumat de algoritmul lui Cannon este acelai cu cel din cazul implementarii standard dar consumul de memorie este mult mai mic.

43

Sisteme de ecuatii
Sistem standard de ecuatii liniare

Reducerea la forma triunghiular (eliminarea Gaussiana) Algorimul eliminrii Gaussiene


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

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

Implementare pe o reea liniar


Cele n procesoare se noteaz cu P0,.Pn-1. Fiecare procesor Pi dispune n memoria local de coeficienii ecuaiei a i-a (linia i a matricei A a sistemului de ecuaii plus bi). Timpul consumat n iteraia k este (n-k-1). Rezulta pentru ntregul algoritm timpul (n2). Implementarea pasului de divizare for j = k+1 to n-1 do A[k,j] = A[k,j] / A[k,k]; y[k] = b[k] / A[k,k]; A[k,k] = 1;

45

Difuzia unu-la-toi a liniei A[k,*]

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

Drumuri optime n grafuri


Problema algebric a drumurilor
Problema algebric a drumurilor unific strategiile utilizate n rezolvarea a trei clase mari de probleme, fiecare dezvoltat independent, cu algoritmi proprii: determinarea nchiderii tranzitive a unei relaii, determinarea drumurilor minime ntr-un graf, rezolvarea sistemelor de ecuaii liniare (metoda Gauss-Jordan).

Algoritmul lui Kucera


Algoritmul lui Kucera, dezvoltat pentru o main CREW-PRAM, pentru problema drumurilor minime poate fi considerat de baz. Algoritmul utilizeaz ca date iniiale matricea Dnn a ponderilor muchiilor unui graf G = (V,E), (V = {1,...,n}, E VV). i,j {1,...,n} : D[i,j] = dij, dac (i,j) E , dac (i,j) E unde dij este distana dintre nodurile i i j (dii = 0). Rezultatul execuiei algoritmului lui Kucera este o matrice Cnn unde : C[i,j] = 0, dac i = j C[i,j] = min[((i,i1,,ik,j)) || drum in G] { D[i,i1]+D[i1,i2]++D[ik,j]}, dac i j Pseudocod pentru Algoritmul lui Kucera for all i,j:1 i,j n par do C[i,j] = D[i,j]; repeat logn times for all i,j,k: 1 i,j,k n par do w[i,k,j] = C[i,k]+C[k,j]; for all i,j: 1 i,j n par do C[i,j] = min{C[i,j],mink{w[i,k,j]; k = 1,...,n}}; end

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

Definirea problemei algebrice a drumurilor


Inlocuind n algoritmul lui Kucera: D cu A (matricea de adiacen), + cu i logic, min cu sau logic, se obtine un algoritm pentru problema nchiderii tranzitive. Menionm c pentru un graf G, problema nchiderii tranzitive const n determinarea unei matrice Cnn: C[i,j] = 1, daca exista n G un drum de la i la j 0, altfel Generaliznd, problema algebric a drumurilor poate fi definit astfel: Dat fiind un graf ponderat G = (V,E,w), unde w:E H, iar (H,,) formeaz un inel cu unitate, s se determine matricea Cnn astfel nct C[i,j] = w(p)
[p drum de la i la j]

Obs. Daca p = i0i1...ik-1ik atunci w(p) = w[il,il+1].


l =0

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

Algoritmul sistolic al lui Y. Robert si D. Trystram

Retea sistolic pentru drumuri cu aceeai surs


Reeaua lui Y. Robert i D. Trystram poate fi modificat si adaptat problemei drumurilor de cost minim sau maxim de la un nod n la toate celelalte n-1 noduri ale unui digraf aciclic G = (V,A), V = {1,2,...,n}, E VV. Vom prezenta n continuare o arhitectur sistolic pentru problema drumurilor de cost maxim de la un nod n la toate celelalte n-1 noduri ale unui digraf aciclic G = (V,A). Iniial, Cnn este matricea Costnn a costurilor arcelor digrafului aciclic G. cij este costul arcului (i,j).

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

Exemplu de aplicare a algoritmului de drumuri maxime ntr-un digraf aciclic

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

PARTEA a-II-a INTRODUCERE N MPI Autor: Cristian-Mihai Amarandei

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(); }

Tipuri de date n MPI


Trebuie observat faptul c de fiecare dat este specificat lungimea mesajului ca numr de intrri, nu ca numr de bytes. Tipurile de date de baz corespund tipurilor de date de baz ale limbajului folosit. Astfel avem: MPI datatype MPI_CHAR MPI_SHORT MPI_INT MPI_LONG C datatype signed char signed short int signed int signed long int 59

MPI_UNSIGNED_CHAR MPI_UNSIGNED_SHORT MPI_UNSIGNED MPI_FLOAT MPI_DOUBLE MPI_LONG_DOUBLE MPI_BYTE MPI_PACKED

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.

Comunicarea ntre procese


O mare parte a funciilor MPI sunt funcii pentru comunicare. Comunicrile ntre procese pot fi punct-la-punct (blocante sau neblocante) sau colective.

Comunicri unu-la-unu blocante


O funcie de comunicare cu blocare se ncheie odat cu trimiterea, respectiv primirea, mesajului chiar dac operaia n sine nu s-a terminat. Astfel, la apelul funciei MPI_Send execuia poate continua dup revenirea din apel chiar dac mesajul nu a ajuns nc la destinaie. Asemntor, se revine din apelul funciei MPI_Recv imediat ce mesajul a ajuns n zona de destinaie i poate fi citit. Apelul unei funciei send:
MPI_Send(buf, count, datatype, dest, tag, comm) [IN [IN [IN [IN [IN [IN buf] count] datatype] dest] tag] comm] - adresa zonei tampon pentru datele de trimis - numrul datelor de trimis - tipul datelor pentru fiecare element din buffer - identificatorul procesului destinaie - identificator de mesaj - comunicatorul

Prototipul funciei este urmtorul:


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

Apelul unei funciei receive:


MPI_Recv(buf, count, datatype, source, tag, comm, status)

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

Prototipul funciei este urmtorul:


int MPI_Recv(void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status)

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(); }

Moduri de realizare a comunicrii


Exist mai multe moduri de realizare a comunicrii, indicate n numele funciei printr-o liter astfel: B pentru bufferat, S sincron i R pentru ready. 61

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

Prototipul funciei este urmtorul:


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

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

Prototipul funciei este urmtorul:


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

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

Prototipul funciei este urmtorul:


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

Observaie: n modul ready apelul receive trebuie s-l precead obligatoriu pe cel pentru operaia send.

63

Comunicri unu-la-unu neblocante


Un mecanism care adesea confer performae mai bune este utilizarea comunicrilor neblocante. Comunicrile neblocante pot fi folosite n cele patru moduri ca i cele blocante: standard, bufferate, sincrone i ready. Modul de folosire este acelai cu cel de la comunicrile blocante. Cu excepia modului ready operaia send neblocant poate fi iniiat chiar dac o operaie receive a fost sau nu iniiat, iar una de tip ready numai dac a fost iniiat o operaie receive. n toate cazurile operaia send este iniiat local i returneaz imediat indiferent de starea altor procese. Apelul funciei Isend:
MPI_Isend(buf, count, datatype, dest, tag, comm, request) [ [ [ [ [ [ [ IN buf] IN count] IN datatype] IN dest] IN tag] IN comm] OUT request] - 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

Prototipul funciei este urmtorul:


int MPI_Isend(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request)

Apelul funciei Irecv:


MPI_Irecv(buf, count, datatype, source, tag, comm, request) [ IN buf] [ [ [ [ [ [ IN count] IN datatype] IN source] IN tag] IN comm] OUT request] - adresa zonei tampon (bufferului) pentru datele de trimis - numrul de elemente de trimis din buffer - tipul datelor pentru fiecare element din buffer - identificatorul procesului surs - identificator de mesaj - comunicatorul - communication request

Prototipul funciei este urmtorul:

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

MPI_Wait(req1, status); } else if (myrank == 1) { nt b; MPI_Irecv(&b,1,MPI_INT,0,mesgtag,MPI_COMM_WORLD,req1); MPI_Wait(req1, status); }

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

Alte funcii neblocante: MPI_Ibsend, MPI_Issend, MPI_Irsend. Apelul funciei Ibsend:


MPI_Ibsend(buf,count,datatype,dest,tag,comm,request) [ IN buf] [ [ [ [ [ [ IN count] IN datatype] IN dest] IN tag] IN comm] OUT request] - adresa zonei tampon (bufferului) pentru datele le trimis - numrul de elemente de trimis din buffer - tipul datelor pentru fiecare element din buffer - rangul destinaiei - identificator de mesaj - comunicatorul - communication request

Prototipul funciei este urmtorul:


int MPI_Ibsend(void* buf, int count, MPI_Datatype datatype,int dest, int tag, MPI_Comm comm, MPI_Request *request)

Apelul funciei Issend:


MPI_Issend(buf,count,datatype,dest,tag,comm,request) [ IN buf] [ [ [ [ [ [ IN count] IN datatype] IN dest] IN tag] IN comm] OUT request] - 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

Prototipul funciei este urmtorul:


int MPI_Issend(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request)

Apelul funciei Irsend:


MPI_IRSEND(buf,count,datatype,dest,tag,comm,request)

68

[ [ [ [ [ [ [

IN buf] IN count] IN datatype] IN dest] IN tag] IN comm] OUT request]

- 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

Prototipul funciei este urmtorul:


int MPI_Irsend(void* buf, int count, MPI_Datatype datatype,int dest, int tag, MPI_Comm comm, MPI_Request *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

Prototipul funciei este:


int MPI_Barrier(MPI_Comm comm )

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

Prototipul funciei este urmtorul:


int MPI_Bcast(void* buffer, int count, MPI_Datatype datatype, int root, MPI_Comm comm )

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(&param,1,MPI_DOUBLE,5,MPI_COMM_WORLD); printf("P:%d dupa broadcast parametrul este %f\n", rank, param); MPI_Finalize(); }

71

Comunicarea personalizat unu-la-toi (Scatter)


Procesul root trimite coninutul sendbuffer-ului ctre toate procesele din grup (inclusiv ctre procesul root).
P0

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

Prototipul funciei este urmtorul:


MPI_Scatter(void* sendbuf, int sendcount, MPI_Datatype sendtype, void* recvbuf, int recvcount, MPI_Datatype recvtype,int root, MPI_Comm comm)

Observaii: - ntr-o alt interpretare, funcia Scatter ar nsemna: procesul root trimite un mesaj cu MPI_Send(sendbuf, sendcount-n, 72

sendtype, ...). Acest mesaj este mprit n n segmente egale,

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

100 Procesul root

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);

Comunicarea toi-la-unu - Colectarea datelor (Gather)


Fiecare proces (inclusiv procesul root) trimite coninutul sendbufferului ctre procesul root. Procesul root primete mesajele i le stocheaz n ordinea rangului.

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

[ IN recvcount] [ IN recvtype] [ IN root] [ IN comm]

Prototipul funciei este urmtorul:


int MPI_Gather(void* sendbuf, int sendcount, MPI_Datatype sendtype, void* recvbuf, int recvcount, MPI_Datatype recvtype, int root,MPI_Comm comm)

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

100 Procesul root

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

Apelul funciei Reduce:


MPI_Reduce(sendbuf,recvbuf,count,datatype,op,root,comm) [IN sendbuf] [OUT recvbuf] [IN [IN [IN [IN [IN count] datatype] op] root] comm] - adresa buffer-ului de trimitere - adresa buffer-ului de recepie (are semnificaie numai pentru procesul root) - numrul de elemente din buffer-ul de trimitere - tipul datelor din buffer-ul de trimitere - operaia de reducere - rangul procesului root - comunicator

Prototipul funciei este urmtorul:


int MPI_Reduce (void* sendbuf, void* recvbuf, int count, MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm)

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

A0+A1+A2 A0+A1+A2 A0+A1+A2

B0+B1+B2 B0+B1+B2 B0+B1+B2

C0+C1+C2 C0+C1+C2 C0+C1+C2

P0

P1 P2

P1 P2

Apelul funciei Allreduce:


MPI_Allreduce(sendbuf,recvbuf,count,datatype,op, comm) [IN sendbuf] [OUT recvbuf] [IN count] [IN datatype] [IN op] [IN comm] - adresa buffer-ului de trimitere - adresa buffer-ului de recepie - numrul de elemente din buffer-ul de trimitere - tipul datelor din buffer-ul de trimitere - operaia de reducere - comunicator

Prototipul funciei este urmtorul:

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

A0+A1+A2 B0+B1+B2 C0+C1+C2

P0

P1 P2

P1 P2

Apelul funciei Allreduce:


MPI_Reduce_scatter(sendbuf,recvbuf,recvcounts,datatype,op, comm) [ IN sendbuf] [ OUT recvbuf] [ IN recvcounts] [ IN datatype] [ IN op] [ IN comm] - adresa buffer-ului de trimitere - adresa buffer-ului de recepie - ir de ntregi care specific numrul de elemente din rezultatul distribuit ctre fiecare proces. Acesta trebuie sa fie identic la toate apelurile din procese. - tipul datelor din buffer-ul de trimitere - operaia de reducere - comunicator

Prototipul funciei este urmtorul:


int MPI_Reduce_scatter (void* sendbuf, void* recvbuf, int *recvcounts, MPI_Datatype datatype, MPI_Op op, MPI_Comm comm)

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

MPI_LOR MPI_BOR MPI_LXOR MPI_BXOR MPI_MAXLOC MPI_MINLOC

sau logic sau pe bii xor logic xor pe bii max value and location min value and location

Operaii de reducere definite de utilizator


Definirea unei operaii de ctre utilizator se face folosind funcia MPI_Op_create: Apelul funciei este:
MPI_Op_create( function, commute, op) [IN function] [IN commute] [OUT op]

1. funcia definit de utilizator 2. daca este comutativ true, altfel false 3. operaia

Prototipul funciei este urmtorul:


int MPI_Op_create (MPI_User_function *function, int commute, MPI_Op *op)

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);

Exemplu: Un exemplu de implementare ineficient a funciei MPI_Reduce:


if (rank > 0) { MPI_Recv(tempbuf, count, datatype, rank-1,...)

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

Prototipul funciei este urmtorul:


int MPI_op_free ( MPI_Op *op)

Exemplu: Calculul produsului unui ir de numere complexe (vezi i anexa):


typedef struct { double real,imag; } Complex; /* funcia definit de utilzator */ void myProd( Complex *in, Complex *inout, int *len, MPI_Datatype *dptr) { int i;

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

Apelul funciei este:


MPI_Scan( sendbuf, recvbuf, count, datatype, op, comm ) [ [ [ [ [ [ IN sendbuf] OUT recvbuf] IN count] IN datatype] IN op] IN comm] adresa buffer-ului de trimitere adresa buffer-ului de recepie numarul de elemente din buffer-ul de trimitere tipul datelor din buffer-ul de trimitere operaia comunicator

Prototipul funciei este urmtorul:


int MPI_Scan(void* sendbuf, void* recvbuf, int count, MPI_Datatype datatype, MPI_Op op, MPI_Comm comm )

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.

Corectitudinea operaiilor colective


Un program scris corect n MPI trebuie s apeleze comunicrile colective astfel nct s nu apar blocaje, indiferent dac comunicrile sunt sincronizate sau nu. n urmtorul exemplu este artat un mod de folosire a operaiilor colective care poate duce la blocare. Exemplu: Utilizare greit:
switch(rank) { case 0: MPI_Bcast(buf1, MPI_Bcast(buf2, break; case 1: MPI_Bcast(buf2, MPI_Bcast(buf1, break;

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

O prim execuie posibil Proces

O a doua execuie posibil

Exemplu: Folosirea operaiilor de reducere predefinite MPI_MAXLOC i MPI_MINLOC:


#include <mpi.h> /* Rulare pentru 16 procese */ struct { double value; int rank; } in, out; void main (int argc, char *argv[]) { int rank; int root; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD,&rank); in.value=rank+1; in.rank=rank; root=7; MPI_Reduce(&in,&out,1,MPI_DOUBLE_INT,MPI_MAXLOC,root, MPI_COMM_WORLD); if(rank==root) printf("PE:%d max=%lf la rangul %d\n", rank, out.value, out.rank); MPI_Reduce(&in,&out,1,MPI_DOUBLE_INT,MPI_MINLOC,root, MPI_COMM_WORLD); if(rank==root) printf("PE:%d min=%lf la rangul %d\n", rank, out.value, out.rank); MPI_Finalize(); }

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(); }

Tipuri de date definite de utilizator


Mecanismele de Comunicare implementate n MPI ce au fost studiate pn acum permit numai transmiterea i recepia secvenelor de elemente identice care reprezint zone continue n memorie. Este de 86

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.

Constructori pentru tipuri de date


Cel mai simplu constructor este MPI_TYPE_CONTIGUOUS care permite multiplicarea unui tip de date n locaii continue. Apelul funciei Contiguous:
MPI_Type_contiguous (count, oldtype, newtype) [IN count] [IN oldtype] [OUT newtype] - numrul de copii (ntreg pozitiv) - tipul de dat iniial - tipul de dat nou

Prototipul funciei este urmtorul:


int MPI_Type_contiguous(int count, MPI_Datatype oldtype,MPI_Datatype *newtype)

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.

oldtype count = 3, blocklength = 2, newtype Apelul funciei Vector:


MPI_Type_vector ( count, blocklength, stride, oldtype, newtype) [IN count] [IN blocklength] [IN stride] [IN oldtype] [OUT newtype] - numrul de blocuri - numrul de elemente din fiecare bloc - spaiul dintre blocuri, msurat n numrul de elemente (ntreg) fiecrui bloc - tipul de dat vechi - tipul de dat nou

Prototipul funciei este urmtorul:


int MPI_Type_vector(int count, int blocklength, int stride, MPI_Datatype oldtype, MPI_Datatype *newtype)

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

Prototipul funciei este urmtorul:


int MPI_Type_hvector(int count, int blocklength, MPI_Aint stride,MPI_Datatype oldtype, MPI_Datatype *newtype)

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

[IN array_of_blocklengths] [IN array_of_displacements] [IN oldtype] [OUT newtype]

Prototipul funciei este urmtorul:


int MPI_Type_indexed(int count, int *array_of_blocklengths, int *array_of_displacements, MPI_Datatype oldtype,MPI_Datatype *newtype)

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

oldtype count = 3, blocklength = (2,3,4), deplasament = newtype Apelul funciei Struct:


MPI_TYPE_STRUCT(count, array_of_blocklengths, array_of_displacements, array_of_types, newtype) [IN count] [IN array_of_blocklength] [IN array_of_displacements] [IN array_of_types] [OUT newtype] - numrul de blocuri; de asemenea i numrul de intrri n array_of_types, array_of_displacements i array_of_blocklengths - numrul de elemente din fiecare bloc - deplasamentul n bii al fiecrui bloc - tipul elementelor din fiecare bloc - noul tip de dat

Prototipul funciei este urmtorul:


int MPI_Type_struct(int count, int *array_of_blocklengths,MPI_Aint *array_of_displacements, MPI_Datatype *array_of_types, MPI_Datatype *newtype)

Exemplu :Trimiterea unui tablou de structuri:


struct Partstruct { int class; /* clasa particulei */ double d[6]; /* coordonatele */ char b[7]; /* informaii suplimentare*/ }; struct Partstruct int MPI_Comm comm; particle[1000]; dest, tag;

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

Utilizarea tipurilor de date derivate


Un tip de dat trebuie creat nainte de a putea fi utilizat n Comunicri. Un tip de date creat mai poate fi folosit ca argument n construirea unor noi tipuri de date. Observaie: Nu este necesar creearea tipurilor de date de baz. Pentru crearea tipurilor de date se folosete funcia:
MPI_ Type_commit (datatype) [INOUT datatype] - tipul de date care trebuie creat

Prototipul funciei este urmtorul:


int MPI_Type_commit(MPI_Datatype *datatype)

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

Prototipul funciei este urmtorul:


int MPI_Type_free( MPI_Datatype *datatype )

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

[IN datatype] [OUT count]

Prototipul funciei este:


MPI_Get_elements(MPI_Status *status, MPI_Datatype datatype, int *count)

Exemplu: Lucrul cu structuri de date de tip union:


union { int ival; float fval; } u[1000] int /* utype; Variabila utype pstreaz urma tipului curent */ type[2]; blocklen[2] = {1,1}; disp[2]; mpi_utype[2]; i,j;

MPI_Datatype int MPI_Aint MPI_Datatype MPI_Aint

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);

Topologii pentru procese


O topologie ofer un mecanism convenabil pentru denumirea proceselor dintr-un grup, i n plus, poate oferi soluii sistemului, n timpul rulrii, pentru maparea proceselor pe hardware-ul existent. Un grup de procese n MPI reprezint o colecie de n procese. Fiecare proces din grup are atribuit un rang ntre 0 i n-1. n multe aplicaii paralele o atribuire liniar a acestor ranguri nu reflect logica comunicrilor. Adesea rangurile proceselor sunt aranjate n topologii ca de exemplu o plas cu 2 sau 3 dimensiuni. Mai general, logica aranjrii proceselor este descris de un graf. n continuare, aceast aranjare logic a proceselor o vom numi: topologie virtual.

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

Prototipul funciei este urmtorul:


int MPI_Cart_create(MPI_Comm comm_old, int ndims, int *dims, int *periods, int reorder, MPI_Comm *comm_cart)

Exemplu: Implementarea topologiei de tor virtual din figura anterioar :


MPI_Comm vu; int dim[2], period[2], reorder; 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);

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

Prototipul funciei este urmtorul:


int MPI_Dims_create(int nnodes, int ndims, int *dims)

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

Prototipul funciei este:


int MPI_Cartdim_get(MPI_Comm comm, int *ndims) MPI_Cart_rank (comm, coords, rank) [IN comm] - comunicator cu structura cartezian [IN coords] - tablou de ntregi care specific coordonatele carteziene ale unui proces [OUT rank] - rangul procesului specificat

97

Prototipul funciei este urmtorul:


int MPI_Cart_rank(MPI_Comm comm, int *coords, int *rank)

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

Prototipul funciei este urmtorul:


int MPI_Cart_get(MPI_Comm comm, int maxdims, int *dims, int *periods, int *coords)

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

Crearea topologiei de tip graf


MPI_Graph_create(comm_old, nnodes, index, edges, reorder, comm_graph) [ [ [ [ [ [ IN comm_old] IN nnodes] IN index] IN edges] IN reorder] OUT comm_graph] comunicator de intrare numarul de noduri in graf tablou de ntregi care descriu nodurile tablou de ntregi care descriu muchiile grafului rangurile pot fi ordonate sau nu comunicator cu topologia grafului

Prototipul funciei este urmtorul:


int MPI_Graph_create(MPI_Comm comm_old, int nnodes, int *index, int *edges, int reorder, MPI_Comm *comm_graph)

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

Prototipul funciei este urmtorul:


int MPI_Graphdims_get(MPI_Comm comm, int *nnodes, int *nedges) MPI_Graph_get (comm, maxindex, maxedges, index, edges) [ IN comm] [ IN maxindex] [ IN maxedges] [ OUT index] [ OUT edges] comunicator cu structura grafului lungimea vectorului index n programul apelant lungimera vectorului edges n programul apelant tablou de ntregi ce conine structura grafului tablou de ntregi ce conine structura grafului

Prototipul funciei este urmtorul:


int MPI_Graph_get(MPI_Comm comm, int maxindex, int maxedges, int *index, int *edges)

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

Prototipul funciei este urmtorul:


int MPI_Graph_neighbors_count(MPI_Comm comm, int rank, int *nneighbors) MPI_Graph_neighbors (comm, rank, maxneighbors, neighbors)

101

[ IN comm] [ IN rank] [ IN maxneighbors] [ OUT neighbors]

comunicator cu topologia grafului rangul procesului din grupul comm mrimea tabloului cu vecinii rangurile proceselor care au ca vecini procesul specificat

Prototipul funciei este urmtorul:


int MPI_Graph_neighbors(MPI_Comm comm, int rank, int maxneighbors, int *neighbors)

Funcii pentru interogarea topologiei


Extragerea informaiilor privind o anumit topologie se poate face cu urmtoarele funcii:
MPI_Topo_test (comm, status) [ IN comm] [ OUT status] - comunicator - tipul topologiei comunicatorului

Prototipul funciei este urmtorul:


int MPI_Topo_test(MPI_Comm comm, int *status)

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

Instructiuni de instalare, compilare i execuie a aplicaiilor care folosesc biblioteca MPI


Pentru instalarea oricrei distribuii de MPI, comercial sau academic, trebuie citite cu atenie instruciunile de instalare puse la dispoziie de productor. Cerinele sistem sunt fie Windows NT 4.0, Windows 2000 sau WindowsXP, fie un sistem Linux (orice distributie) sau Unix. n cazul Linux, productorii RedHat i Suse (Novell) au inclus n distribuia standard versiunea LAM-MPI (http://www.lam-mpi.org/) i/sau MPICH (http://www-unix.mcs.anl.gov/mpi/). Indiferent de producatorul ales, instalarea si comenzile de utilizare sunt aproximativ asemntoare. Pentru a putea folosi n programele proprii funciile puse la dispoziie de biblioteca MPI, n codul sursa trebuie s existe urmtoarea linie:
#include mpi.h

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

4. Se modific calea pentru includerea bibliotecilor (de exemplu: C:\Program Files\MPICH\SDK\include).

5. Se modific calea pentru includerea fisierelor obiect asociate bibliotecii mpi.h (de exemplu: C:\Program Files\MPICH\SDK\lib )

105

6. Se adaug mpich.lib la proiect

7. Se nchide casua de dialog cu setarile proiectului

106

8. Se adaug fiierele surs la proiect.

9. Compilare Rularea aplicaiei se face folosind fie interfaa grafic guiMPIRun,

fie linia de comanda


C:\Program Files\MPICH\bin\mpd\mpirun np 4 prog.exe

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

Rularea unei aplicaii pentru 4 procese:


mpirun np 4 prog

LAM-MPI: Compilarea folosind GNU C Compiler (gcc):


mpicc o prog prog.c

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

Verificarea faptului ca LAM a fost pornit se va face folosind:


recon -v lamhosts

Rularea programelor:
mpirun-lam -np 4 prog

n caz de eroare se folosete comanda


lamcleen

Pentru oprirea LAM:


wipe v lamhosts
1

Numele si adresele IP sunt pentru exemplificare

108

PARTEA a-III-a EXEMPLE DE PROGRAME MPI Autor: Bogdan Romanescu

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); }

MPI_Recv(&new_val, 1, MPI_INT, pi+j, 99, MPI_COMM_WORLD, &status); }

/******** 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

A[pf]; = A[pi]; = aux;

A[pf]; = A[pi]; = aux;

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

data_send = A[myrank]; A[myrank] = data_recv; }

/************ 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;

a[left] = a[right]; a[right] = aux; }

//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); } }

mai && ) src,

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

for(j=0; j<Q; j++) L[k][j] = recv[k][j];

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

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