Sunteți pe pagina 1din 121

UNIVERSITATEA DE STAT DIN MOLDOVA

Facultatea de Matematic i Informatic


Departamentul Matematic
Boris HNCU, Elena CALM

MODELE DE PROGRAMARE
PARALEL PE CLUSTERE
PARTEA I. PROGRAMARE MPI

Note de curs

Aprobat de Consiliul Calitii al USM

CEP USM
Chiinu, 2016
CZU .................
C ......................
Recomandat de Consiliul Facultii de Matematic i Informatic

Autori: Boris HNCU, Elena CALM


Recenzent: Grigore SECRIERU, doctor n tiine fizico-
matematice, confereniar universitar,
cercettor tiinific coordonator IMI al
AM.

Descrierea CIP a Camerei Naionale a Crii

Boris HNCU
Modele de programare paralela pe clustere / Elena Calm;
Universitatea de Stat din Moldova. Facultatea de Matematic i
Informatic, Departamentul Matematici Aplicate Ch.: CEP USM,
2016.

.......... ex.
ISBN ...........................

...........................
...........................

B. HNCU, E. CALM, 2016


CEP USM, 2016
ISBN ..................

2
Cuprins
Introducere.................................................................................................... 7
CAPITOLUL 1. SISTEME I MODELE DE CALCUL PARALEL ........ 11
1.1 Clasificarea sistemelor de calcul paralel .......................................... 11
1.2 Definiia algoritmilor paraleli ........................................................... 15
CAPITOLUL 2. MODELE DE PROGRAMARE PARALEL ............... 17
2.1 Sisteme de calcul cu memorie distribuit i partajat ....................... 17
2.2 Particulariti de baz ale modelor de programare paralel cu
memorie partajat ................................................................................... 19
CAPITOLUL 3. MODELUL MPI DE PROGRAMARE PARALEL PE
SISTEME DE CALCUL CU MEMORIE DISTRIBUIT ........................ 22
3.1 Generaliti ....................................................................................... 22
3.2 Funciile MPI de baz ....................................................................... 24
3.3 Comunicarea de tip proces-proces (punct la punct) ...................... 27
3.3.1 Operaii de baz ......................................................................... 27
3.3.2 Funcii MPI pentru transmiterea mesajelor ............................... 28
3.3.3 Exerciii ..................................................................................... 32
3.4 Funciile MPI pentru comunicarea colectiv .................................... 32
3.4.1 Funciile MPI pentru asamblarea (adunarea) datelor................. 37
3.4.2 Funciile MPI pentru difuzarea (distribuirea) datelor ................ 41
3.4.3 Funciile MPI pentru operaii de reducere ................................. 47
3.4.4 Exerciii ..................................................................................... 60
3.5 Rutine MPI de administrare a comunicatorului i a grupelor ........... 61
3.5.1 Grupe i comunicatori ............................................................... 61
3.5.2 Funciile MPI de gestionare a grupelor de procese ................... 62
3.5.3 Funciile MPI de gestionare a comunicatoarelor ....................... 66
3.5.4 Topologii virtuale. Rutine de creare a topologiei carteziene ..... 69
3.5.5 Exerciii ..................................................................................... 76
3.6 Accesul distant la memorie. Funciile RMA .................................... 77
3.6.1 Comunicare unic-direcionat.................................................... 77
3.6.2 Funciile RMA ........................................................................... 79
3.6.3 Exerciii ..................................................................................... 84
3.7 Generarea dinamic a proceselor MPI.............................................. 84

3
3.7.1 Modaliti de generare dinamic a proceselor ........................... 84
3.7.2 Comunicarea ntre procesele generate dinamic ......................... 92
3.7.3 Exerciii ..................................................................................... 98
3.8 Tipuri de date MPI............................................................................ 98
3.8.1 Harta tipului de date .................................................................. 98
3.8.2 Tipuri derivate de date ............................................................. 100
3.8.3 Exerciii ................................................................................... 105
3.9 Utilizarea fiierelor n MPI ............................................................. 106
3.9.1 Operaiile de intrare/ieire (I/O) n programe MPI .................. 106
3.9.2 Funciile MPI pentru utilizarea fiierelor ................................ 109
3.9.3 Exerciii ................................................................................... 115
Bibliografie ............................................................................................... 117
Anex........................................................................................................ 118

4
Terminologie general pentru calcul i programare
paralel
Ca orice sector de activitate, calculul paralel i programarea paralel
are propriul jargon. n continuare sunt dai civa termeni de uz obinuit
n calculul paralel.
Task o seciune logic discret a efortului de calcul. Tipic, un task
este un program sau un set de instruciuni asemntoare unui program care
este executat de un procesor.
Task paralel un task care poate fi executat de procesoare multiple n
condiii de siguran (care genereaz rezultate corecte).
Execuie serial executarea secvenial a unui program, declaraie
cu declaraie. n sensul cel mai simplu, este ceea ce se ntmpl ntr-o
main cu un singur procesor. Virtual, i cnd toate task-urile sunt paralele,
exist seciuni ale unui program paralel care trebuie executate serial.
Execuie paralel execuie a unui program prin mai mult dect un
task, cu fiecare task capabil s execute concomitent aceeai declaraie sau
declaraii diferite.
Memorie partajat din punct de vedere strict hardware, este o
arhitectur n care toate procesoarele au acces direct (i de obicei bazat pe
bus) la memoria fizic comun. n sensul programrii, formula descrie un
model n care task-urile paralele au toate aceeai imagine a memoriei i
se pot adresa i pot accesa direct aceleai locaii logice de memorie,
indiferent unde exist memoria fizic n realitate.
Memorie distribuit n sensul hardware se refer la accesul
memoriei, care fizic nu este comun, printr-o reea. Ca model de
programare, task-urile pot vedea logic numai memoria local a mainii i
trebuie s apeleze la comunicare pentru a avea acces la memoria altor
maini unde alte task-uri sunt n curs de execuie.
Comunicare obinuit, task-urile paralele trebuie s schimbe date.
Exist mai multe moduri n care se poate comunica: printr-un bus al
memoriilor utilizat n indiviziune (partajat) sau printr-o reea. Indiferent de
modalitatea utilizat, schimbul real de date este denumit uzual comunicare.
Sincronizare coordonarea task-urilor paralele n timp real, foarte
frecvent asociat cu comunicarea. Implementat adesea prin stabilirea unui
punct de sincronizare n aplicaie unde un task nu poate continua pn cnd
alt(-e) task(-uri) nu ating(-e) acelai punct sau acelai punct echivalent
5
logic. Sincronizarea implic uzual ateptri cel puin pentru un task i poate
cauza prin aceasta o cretere a timpului de execuie (timp universal ceasul
din perete) a aplicaiei paralele.
Granularitate n calculul paralel, granularitatea este o msur
calitativ a raportului calcul/comunicare. Granularitatea poate fi:
grosier: relativ mult calcul ntre fazele (evenimentele) de
comunicare.
fin: relativ puin calcul ntre fazele (evenimentele) de comunicare.
Accelerarea observat a unui cod care a fost paralelizat se definete
ca raportul:
timpul dup ceasul din perete la execuia serial
timpul dup ceasul din perete la execuia paralel
Este unul din cei mai simpli i mai larg utilizai indicatori ai
performanelor unui program paralel.
Overhead-ul paralel durata necesar pentru a coordona task-urile
paralele, n opoziie cu executarea de lucru util. Overhead-ul paralel poate
include elemente de genul:
timpul de pornire a task-ului;
sincronizrile;
comunicarea de date;
overhead-ul software impus de compilatoarele paralele, biblioteci,
instrumente (tools), sistemul de operare etc.;
timpul de ncheiere a task-ului.
Masiv paralel se refer la un sistem hardware care constituie un
sistem paralel dat, care are multe procesoare. Semnificaia lui multe este
n cretere, dar n prezent multe nseamn de la 1000 de procesoare n
sus.
Scalabilitate se refer la capacitatea unui sistem paralel (hardware
i/sau software) de a demonstra n accelerarea (speedup) paralel o cretere
proporional odat cu ataarea mai multor procesoare. Factori care
contribuie la scalabilitate:
hardware n particular lrgimea de band i reeaua de
comunicare;
algoritmul aplicaiei;
overhead-ul paralel asociat;
caracteristicile specifice ale aplicaiei i ale programului.

6
Introducere
Tehnologiile informaionale servesc drept suport (instrumentariu) pentru
rezolvarea diferitor probleme teoretice i practice generate de activitatea
uman. n general, procesul de rezolvare a problemelor cu caracter aplicativ
poate fi reprezentat prin urmtoarea schem:

Descrierea verbal a Elaborarea unui model


problemei (de obicei comanda- al problemei
tarul o descrie unui specialist) (de ex. un model matematic)
proces iterativ

Elaborarea softului necesar Analiza matematic a


pentru implementarea modelului, identificarea
algoritmului pe calculator problemei matematice,
elaborarea algoritmului de
soluionare

Experiene numerice Rezultate finale

O vast clas de probleme sunt de o complexitate foarte mare att sub


aspect de volum de date, ct i sub cel al numrului de operaii. Astfel,
apare o necesitate stringent de a utiliza sisteme de calcul performant sau
supercalculatoare. n prezent cele mai utilizate supercalculatoare sunt
calculatoarele paralele de tip cluster.
n baza proiectului CRDF/MRDA Project CERIM-1006-06 la
Facultatea de Matematic i Informatic a Universitii de Stat din
Moldova a fost creat un laborator pentru utilizarea sistemelor paralele de
calcul de tip cluster. Scopul acestui laborator este implementarea n
procesul de instruire i cercetare a noilor tehnologii pentru elaborarea
algoritmilor paraleli i executarea lor pe sisteme locale de tip cluster i
chiar pe sisteme regionale de tip Grid. Actualmente laboratorul conine
urmtoarele echipamente de calcul i de infrastructur:
1 server HP ProLiant DL380 G5
CPU: 2x Dual Core Intel Xeon 5150 (2.7GHz)
7
RAM: 8GB
HDD: 2x72GB, 5x146GB
Network: 3x1Gbps LAN
Acest echipament este utilizat la gestionarea mainilor virtuale necesare
pentru administrarea laboratorului, precum Web Server, FTP Server, PXE
Server, Untangle server etc.
1 server HP ProLiant DL380 G5
CPU: 2x QuadCore Intel Xeon 5420 (2.5 GHz)
RAM: 32GB
HDD: 2x72GB, 6x146GB
Network: 3x1Gbps LAN
2 servere HP ProLiant DL385G1
CPU: 2xAMD 280 Dual-Core 2.4GHz
RAM: 6GB
HDD: SmartArray 6i, 4x146GB
Network: 3x1Gbps LAN
Aceste echipamente sunt utilizate pentru asigurarea logisticii necesare
(la nivel de hard, soft i instruire de resurse umane) la realizarea i
utilizarea MD-GRID NGI i a infrastructurii gridului European (EGI), care
permit extinderea modalitilor de utilizare a clusterului USM pentru
realizarea unor calcule performante.
1 server HP ProLiant DL385G1 i 12 noduri HP ProLiant
DL145R02
CPU: 2xAMD 275 Dual-Core 2.2GHz
RAM: 4GB AECC PC3200 DDR
HDD: 80GB NHP SATA
Network: 3x1Gbps LAN
Storage HP SmartArray 6402
hp StorageWorks MSA20
HDD: 4x500GB 7.2k SATA
Switch HP ProCurve 2650 (48 ports)
Aceste echipamente sunt utilizate pentru elaborarea i implementarea
soft a diferitor clase de algoritmi paraleli, efectuarea cursurilor de prelegeri
i laborator la disciplinele corespunztoare pentru ciclurile 1 i 2 de studii,
precum i n cadrul diferitor proiecte de cercetare.
3 staii de management HP dx5150
CPU: Athlon64 3200+
8
RAM: 1GB PC3200 DDR
Storage: 80GB SATA, DVD-RW
Network: 1Gbps LAN
Monitor: HP L1940 LCD
Aceste echipamente sunt utilizate pentru administrarea clusterului
paralel i a infrastructurii sale de la distan.
14 staii de lucru HP dx5150
CPU: Athlon64 3200+
RAM: 512MB PC3200 DDR
HDD: 80GB SATA
Network: 1Gbps LAN
Monitor: HP L1706 LCD
Tabl interactiv (Smart board) i proiectoare.
Aceste echipamente sunt utilizate drept staii de lucru pentru realizarea
procesului didactic i de cercetare la Facultatea de Matematic i
Informatic.
Clusterul paralel este nzestrat cu sisteme de calcul paralel liceniate sau
open source necesare pentru realizarea procesului didactic i de cercetare
necesar.
Notele de curs Modele de programare paralel. Partea 1. Programare
MPI i propun s acopere minimul de noiuni necesare nelegerii
modalitilor de implementare software a algoritmilor paraleli pe diverse
sisteme paralele de calcul de tip cluster. Cursul va contribui esenial la
dezvoltarea aptitudinilor i capacitilor de construire, studiere a
algoritmilor paraleli i implementarea lor n diferite sisteme paralele de
calcul. Drept rezultat al cunotinelor acumulate, studentul trebuie s poat
aplica cele mai importante metode i rezultate expuse n lucrare, pentru
implementarea celor mai moderne realizri din domeniul informaticii i
tehnologiilor informaionale.
Lucrarea dat acoper obiectivele de referin i unitile de coninut
prezente n Curriculum la disciplina Programare Paralel. Aceast
disciplin, pe parcursul a mai muli ani, este predat la specialitile
Informatica, Managmentul Informaional ale Facultii de Matematic i
Informatic i la specialitatea Tehnologii Informaionale a Facultii de
Fizic i Inginerie.
Aceast lucrare are drept scop dezvoltarea urmtoarelor competene
generice i specifice:
9
cunoaterea bazelor teoretice generale ale matematicii i
informaticii necesare la alctuirea modelului problemei
practice cu caracter socioeconomic;
aplicarea de metode noi de cercetare i implementare soft a
algoritmilor paraleli;
identificarea cilor i a metodelor de utilizare a rezultatelor
obinute i n alte domenii de activitate;
elaborarea i analiza algoritmilor paraleli de soluionare a
problemelor de o mare complexitate;
implementarea metodelor noi i concepii moderne n
realizarea lucrrilor proprii;
posedarea diferitor abiliti de analiz, sintez i evaluare n
abordarea i soluionarea diferitor probleme;
deinerea capacitilor i a deprinderilor necesare pentru
realizarea proiectelor de cercetare, demonstrnd un grad nalt
de autonomie.
Programele prezentate n Exemple au fost elaborate i testate cu
suportul studenilor de la specialitatea Informatic a Facultii de
Matematic i Informatic.
Cunotinele acumulate la acest curs vor fi utilizate i la studierea
cursului Calcul paralel pe clustere pentru studii de masterat.
La elaborarea acestui suport de curs au fost consultate sursele
bibliografice prezente n Bibliografie.

10
CAPITOLUL 1. SISTEME I MODELE DE CALCUL
PARALEL
Obiective
S delimiteze corect clasele de sisteme paralele de calcul i locul lor n
problematica algoritmilor i a programrii paralele;
S defineasc noiunea de algoritmi paraleli;
S poat construi un algoritm paralel n baza unui algoritm secvenial.

1.1 Clasificarea sistemelor de calcul paralel


Calculul paralel, n nelesul cel mai simplu, const n utilizarea
concomitent a unor resurse multiple pentru a rezolva o problem de
calcul sau de gestionare a informaiei n forma electronic. Aceste resurse
pot include urmtoarele componente:
un singur calculator cu mai multe procesoare;
un numr arbitrar de calculatoare conectate la o reea;
o combinaie a acestora.
Problema de calcul trebuie s etaleze uzual caracteristici de genul:
posibilitatea de a fi divizat n pri care pot fi rezolvate
concomitent;
posibilitatea de a fi executate instruciuni multiple n oricare
moment;
perspectiva de a fi rezolvat n timp mai scurt pe resurse de calcul
multiple dect pe o resurs standard unic.
n cazul calculului secvenial analiza i studiul algoritmilor secveniali
este strns legat de structura logic a calculatorului secvenial sau a
mainii de calcul abstracte, maina Turing, de exemplu. Acest lucru este
adevrat i n cazul calcului paralel. Iat de ce nainte de a trece la studierea
metodelor de elaborare i implementare soft a algoritmilor paraleli vom
prezenta acele arhitecturi ale calculatoarelor care corespund clasificrii n
baza taxonomiei lui Flynn.
Clasificarea clasic dup Flynn
Exist modaliti diferite de a clasifica calculatoarele paralele. Una din
cele mai folosite, n uz din 1966, este clasificarea lui Flynn. Clasificarea
Flynn face distincie ntre arhitecturile calculatoarelor cu mai multe
procesoare n raport cu dou caracteristici independente, instruciunile i
datele. Fiecare din aceste caracteristici pot avea una din dou stri: unice

11
sau multiple. Tabelul de mai jos definete cele patru clase posibile potrivit
criteriilor lui Flynn:
SISD SIMD
Single instruction, single data Single instruction, multiple data
(instruciune unic, date unice) (instruciune unic, date multiple)

MISD MIMD
Mutiple instruction, single data Multiple instruction, multiple data
(instruciune multipl, date unice) (instruciune multipl, date multiple)

Facem o succint caracteristic a fiecrei dintre aceste clase.


Clasa SISD
Un calculator din aceast clas const dintr-o singur unitate de
procesare care primete un singur ir de instruciuni ce opereaz asupra
unui singur ir de date. La fiecare pas al execuiei unitatea de control
trimite o instrucie ce opereaz asupra unei date obinute din unitatea de
memorie. De exemplu, instruciunea poate cere procesorului s execute o
operaie logic sau aritmetic asupra datei i depunerea rezultatului n
memorie. Majoritatea calculatoarelor actuale folosesc acest model inventat
de John von Neumann la sfritul anilor 40. Un algoritm ce lucreaz pe un
astfel de model se numete algoritm secvenial sau serial.
Clasa SIMD
Pentru acest model, calculatorul paralel const din N procesoare
identice. Fiecare procesor are propria sa memorie local n care poate stoca
programe sau date. Toate procesoarele lucreaz sub controlul unui singur
ir de instruciuni emise de o singur unitate central de control. Altfel
spus, putem presupune c toate procesoarele pstreaz o copie a
programului de executat (unic) n memoria local. Exist N iruri de date,
cte unul pentru fiecare procesor. Toate procesoarele opereaz sincronizat:
la fiecare pas, toate execut aceeai instruciune, fiecare folosind alte date
de intrare (recepionate din memoria extern). O instruciune poate fi una
simpl (de exemplu, o operaie logic) sau una compus (de exemplu,
interclasarea a dou liste). Analog, datele pot fi simple sau complexe.
Uneori este necesar ca numai o parte din procesoare s execute
instruciunea primit. Acest lucru poate fi codificat n instruciune, spunnd
procesorului dac la pasul respectiv va fi activ (i execut instruciunea) sau
12
inactiv (i atept urmtoarea instruciune). Exist un mecanism de genul
unui ceas global, care asigur sincronizarea execuiei instruciunilor.
Intervalul dintre dou instruciuni succesive poate fi fix sau s depind de
instruciunea ce urmeaz s se execute. n majoritatea problemelor ce se
rezolv pe acest model este necesar ca procesoarele s poat comunica ntre
ele (pe parcursul derulrii algoritmului) pentru a transmite rezultate
intermediare sau date. Acest lucru se poate face n dou moduri: o memorie
comun (Shared Memory) sau folosind o reea de conexiuni
(Interconnection Network).
Shared Memory SIMD
Acest model este cunoscut n literatura de specialitate sub numele de
Modelul PRAM (Parallel Random-Access Machine model). Cele N
procesoare partajeaz o memorie comun, iar comunicarea ntre procesoare
se face prin intermediul acestei memorii. S presupunem c procesorul i
vrea s comunice un numr procesorului j. Acest lucru se face n doi pai.
nti procesorul i scrie numrul n memoria comun la o adres cunoscut
de procesorul j, apoi procesorul j citete numrul de la acea adres. Pe
parcursul execuiei algoritmului paralel, cele N procesoare lucreaz
simultan cu memoria comun pentru a citi datele de intrare, a citi/scrie
rezultate intermediare sau pentru a scrie rezultatele finale. Modelul de baz
permite accesul simultan la memoria comun dac datele ce urmeaz s fie
citite/scrise sunt situate n locaii distincte. Depinznd de modul de acces
simultan la aceeai adres n citire/scriere, modelele se pot divide n:
Modelul EREW SM SIMD (Exclusive-Read, Exclusive-Write). Nu
este permis accesul simultan a dou procesoare la aceeai locaie de
memorie, nici pentru citire, nici pentru scriere.
Modelul CREW SM SIMD (Concurrent-Read, Exclusive-Write).
Este permis accesul simultan a dou procesoare la aceeai locaie de
memorie pentru citire, dar nu i pentru scriere. Este cazul cel mai
natural i mai frecvent folosit.
Modelul ERCW SM SIMD (Exclusive-Read, Concurrent-Write).
Este permis accesul simultan a dou procesoare la aceeai locaie de
memorie pentru scriere, dar nu i pentru citire.
Modelul CRCW SM SIMD (Concurrent-Read, Concurrent-Write).
Este permis accesul simultan a dou procesoare la aceeai locaie de
memorie pentru scriere sau pentru citire.

13
Citirea simultan de la aceeai adres de memorie nu pune probleme,
fiecare procesor poate primi o copie a datei de la adresa respectiv. Pentru
scriere simultan, probabil a unor date diferite, rezultatul este imprevizibil.
De aceea se adopt o regul de eliminare a conflictelor la scrierea simultan
(atunci cnd modelul o permite). Exemple de asemenea reguli pot fi: se
scrie procesorul cu numrul de identificare mai mic sau se scrie suma
tuturor cantitilor pe care fiecare procesor le are de scris, sau se atribuie
prioriti procesoarelor i se scrie procesorul cu cea mai mare prioritate etc.
Clasa MISD
n acest model N procesoare, fiecare cu unitatea sa de control, folosesc
n comun aceeai unitate de memorie unde se gsesc datele de prelucrat.
Sunt N iruri de instruciuni i un singur ir de date. La fiecare pas, o dat
primit din memoria comun este folosit simultan de procesoare, fiecare
executnd instrucia primit de la unitatea de control proprie. Astfel,
paralelismul apare lsnd procesoarele s execute diferite aciuni n acelai
timp asupra aceleiai date. Acest model se potrivete rezolvrii problemelor
n care o intrare trebuie supus mai multor operaii, iar o operaie folosete
intrarea n forma iniial. De exemplu, n probleme de clasificare, este
necesar s stabilim crei clase (de obiecte) i aparine un obiect dat. La
analiza lexical trebuie stabilit unitatea lexical (clasa) creia i aparine
un cuvnt din textul surs. Aceasta revine la recunoaterea cuvntului
folosind automate finite specifice fiecrei uniti lexicale. O soluie
eficient ar fi n acest caz funcionarea simultan a automatelor folosite la
recunoatere, fiecare pe un procesor distinct, dar folosind acelai cuvnt de
analizat.
Clasa MIMD
Acest model este cel mai general i cel mai puternic model de calcul
paralel. Avem N procesoare, N iruri de date i N iruri de instruciuni.
Fiecare procesor execut propriul program furnizat de propria sa unitate de
control. Putem presupune c fiecare procesor execut programe diferite
asupra datelor distincte n timpul rezolvrii diverselor subprobleme n care
a fost mprit problema iniial. Aceasta nseamn c procesoarele
lucreaz asincron. Ca i n cazul SIMD, comunicarea ntre procese are loc
printr-o memorie comun sau cu ajutorul unei reele de conexiuni.
Calculatoarele MIMD cu memorie comun sunt denumite n mod uzual
multiprocesoare (sau tightly coupled machines), iar cele ce folosesc reele

14
de conexiuni se mai numesc multicomputere (sau loosely coupled
machines).
Desigur c pentru multiprocesoare avem submodelele EREW, CREW,
CRCW, ERCW. Uneori multicomputerele sunt denumite sisteme
distribuite. De obicei se face distincie ntre denumiri dup lungimea
conexiunilor (dac procesoarele sunt la distan se folosete termenul de
sistem distribuit).

1.2 Definiia algoritmilor paraleli


Vom introduce urmtoarea definiie a noiunii de algoritm paralel.
Definiie 1.2.1 Prin algoritm paralel vom nelege un graf orientat i
aciclic = (, ), astfel nct pentru orice nod exist o aplicaie
: () , unde denot operaia executat asupra spaiului de
date i () denot adncimea nodului .
Aceast definiie necesit urmtoarele explicaii:
Mulimea de noduri V indic de fapt mulimea de operaii, i
mulimea de arce E indic fluxul informaional.
Toate operaiile { } pentru nodurile v pentru care ()
coincid (adic nodurile de la acelai nivel) se execut n paralel.
Dac () = 0, atunci operaiile : 0 descriu nu altceva
dect atribuire de valori iniiale. Mulimea de noduri pentru care
() = 0 se vor nota prin 0 .
Este clar c aceast definiie este formal i indic, de fapt, ce operaii
i asupra cror fluxuri de date se execut n paralel. Pentru implementarea
algoritmilor paraleli este nevoie de o descriere mai detaliat, i aceasta se
realizeaz prin intermediul unui program de implementare. Acest program
la fel poate fi definit formal folosind un limbaj matematic prin intermediul
unei scheme a algoritmului.
Definiie 1.2.2 Fie = (, ) cu mulimea de noduri iniiale 0 .
Schema (schedule) a algoritmului paralel va conine urmtoarele elemente:
funcia de alocare a procesoarelor : \0 , unde =
{0,1, , 1} indic mulimea de procesoare;
funcia de alocare a timpului : , unde N este timpul discret.
Aceste elemente trebuie s verifice urmtoarele condiii:
i. Dac 0 , rezult c () = 0.
ii. Dac arcul (, ) , rezult c ( ) () + 1.

15
iii. Pentru orie , \0 i , () = ( ), rezult c
() ( ).
n continuare vom nota schema algoritmului paralel prin (, ).
Notm prin problema ce urmeaz a fi rezolvat i prin D volumul de
date necesar pentru rezolvarea problemei. Din definiiile prezentate rezult
c pentru elaborarea i implementarea unui algoritm paralel trebuie:
A) S se realizeze paralelizarea la nivel de date i operaii: problema
se divizeaz ntr-un numr finit de subprobleme { }1 i
volumul de date D se divizeaz n { }1. Cu alte cuvinte, se
construiete graful al algoritmului paralel.
B) S se realizeze distribuirea calculelor: n baza unor algoritmi
secveniali bine cunoscui, pe un sistem de calcul paralel cu n
procesoare, n paralel (sau concurent) se rezolv fiecare
subproblem cu volumul su de date .
C) n baza rezultatelor subproblemelor obinute la etapa B) se
construiete soluia problemei iniiale P.
Parcurgerea acestor trei etape de baz este inevitabil pentru
construirea oricrui algoritm paralel i implementarea soft pe calculatoare
de tip cluster.

16
CAPITOLUL 2. MODELE DE PROGRAMARE
PARALEL
Obiective
S defineasc noiunea de model de programare paralel;
S cunoasc criteriile de clasificare a sistemelor paralele de calcul;
S cunoasc particularitile de baz la elaborarea programelor
paralele ale sistemelor de calcul cu memorie partajat, cu memorie
distribuit i mixte;
S defineasc noiunea de secven critic i s poat utiliza n
programe paralele astfel de secvene.

2.1 Sisteme de calcul cu memorie distribuit i partajat


Model de programare este un set de abiliti de programare (de
elaborare a programelor) utilizate pentru un calculator abstract cu o
arhitectur data. Astfel, modelul de programare este determinat de structura
logic a calculatorului, de arhitectura lui. n prezenta lucrare vor fi studiate
urmtoarele modele de programare paralel:
modele de programare paralel cu memorie distribuit (modele
bazate pe programarea MPI) [n Partea 1 a lucrrii];
modele de programare paralel cu memorie partajat (modele
bazate pe programarea OpenMP) [n Partea 2 a lucrrii];
modele de programare paralel mixte (modele bazate att pe
programarea MPI ct i pe programarea OpenMP) [n Partea 2 a
lucrrii].
Vom descrie mai jos particularitile de baz ale acestor modele.
Calcul paralel este numit execuia n paralel pe mai multe procesoare
a acelorai instruciuni sau i a unor instruciuni diferite, cu scopul
rezolvrii mai rapide a unei probleme, de obicei special adaptat sau
subdivizat. Ideea de baz este aceea c problemele de rezolvat pot fi
uneori mprite n mai multe probleme mai simple, de natur similar sau
identic ntre ele, care pot fi rezolvate simultan. Rezultatul problemei
iniiale se afl apoi cu ajutorul unei anumite coordonri a rezultatelor
pariale.
Calculul distribuit (din englez Distributed computing) este un
domeniu al informaticii care se ocup de sisteme distribuite. Un sistem
distribuit este format din mai multe calculatoare autonome care comunic
printr-o reea. Calculatoarele interacioneaz ntre ele pentru atingerea unui
17
scop comun. Un program care ruleaz ntr-un sistem distribuit se numete
program distribuit, iar procesul de scriere a astfel de programe se numete
programare distribuit.
Sistemele distribuite sunt calculatoarele dintr-o reea ce opereaz cu
aceleai procesoare. Termenii de calcul concurent, calcul paralel i calcul
distribuit au foarte multe n comun. Acelai sistem poate fi caracterizat ca
fiind att paralel ct i distribuit i procesele dintr-un sistem distribuit
tipic ruleaz n paralel. Sistemele concurente pot fi clasificate ca fiind
paralele sau distribuite n funcie de urmtoarele criterii:
n calcul paralel (memorie partajat Shared Memory Model
(SMM)) toate procesoarele au acces la o memorie partajat. Memoria
partajat poate fi utilizat pentru schimbul de informaii ntre
procesoare. Schematic sistemele de tip SMM pot fi reprezentate
astfel:

n calcul distribuit (memorie distribuit Distributed Memory Model


(DMM)) fiecare procesor are memorie proprie (memorie distribuit).
Schimbul de informaii are loc prin trimiterea de mesaje ntre
procesoare. Schematic sistemele de tip DMM pot fi reprezentate
astfel:

18
2.2 Particulariti de baz ale modelelor de programare paralel cu
memorie partajat
Regiuni critice
O regiune critic este o zon de cod care n urma unor secvene de
acces diferite poate genera rezultate diferite. Entitile de acces sunt
procesele sau thread-urile. O regiune critic se poate referi, de asemenea, la
o dat sau la un set de date, care pot fi accesate de mai multe
procese/thread-uri.
O regiune critic la care accesul nu este sincronizat (adic nu se poate
garanta o anumit secven de acces) genereaz o aa-numit condiie de
curs (race condition). Apariia unei astfel de condiii poate genera rezultate
diferite n contexte similare.
Un exemplu de regiune critic este secvena de pseudocod:
move $r1, a valoarea a este adus ntr-un registru,
$r1 <- $r1 + 1 valoarea registrului este incrementat,
write a, $r1 valoarea din registru este rescris n memorie.
Presupunem c avem dou procese care acceseaz aceast regiune
(procesul A i procesul B). Secvena probabil de urmat este:
A: move $r1, a
A: $r1 <- $r1 + 1
A: write a, $r1
B: read $r1
B: $r1 <- $r1 + 1
B: write a, $r1
Dac a are iniial valoarea 5, se va obine n final valoarea dorit 7.
Totui, aceast secven nu este garantat. Cuanta de timp a unui
proces poate expira chiar n mijlocul unei operaii, iar planificatorul de
procese va alege alt proces. n acest caz, o secven posibil ar fi:
A: move $r1,a a este 5
A: $r1<-$r1+1 n memorie a este nc 5, r1 ia valoarea 6
B: move $r1,a a este 5, n r1 se pune valoarea 5
B: $r1<-$r1+1 r1 devine 6
B: move a,$r1 se scrie 6 n memorie
A: move a,$r1 se scrie 6 n memorie
Se observ c pentru valoarea iniial 5 se obine valoarea 6, diferit
de valoarea dorit.
19
Pentru a realiza sincronizarea accesului la regiuni critice va trebui s
permitem accesul unei singure instane de execuie (thread, proces). Este
necesar un mecanism care s foreze o instan de execuie sau mai multe s
atepte la intrarea n regiunea critic, n situaia n care un thread/proces are
deja acces. Situaia este similar unui ghieu sau unui cabinet. O persoan
are permis accesul i, ct timp realizeaz acel lucru, celelalte ateapt. n
momentul n care instana de execuie prsete regiunea critic, o alt
instan de execuie capt accesul.
Excluderea reciproc
Din considerente practice s-a ajuns la necesitatea ca, n contexte
concrete, o secven de instruciuni din programul de cod al unui proces s
poat fi considerat ca fiind indivizibil. Prin proprietatea de a fi
indivizibil nelegem c odat nceput execuia instruciunilor din aceast
secven, nu se va mai executa vreo aciune dintr-un alt proces pn la
terminarea execuiei instruciunilor din acea secven. Aici trebuie s
subliniem un aspect fundamental, i anume c aceast secven de
instruciuni este o entitate logic ce este accesat exclusiv de unul dintre
procesele curente. O astfel de secven de instruciuni este o seciune
critic logic. Deoarece procesul care a intrat n seciunea sa critic
exclude de la execuie orice alt proces curent, rezult c are loc o excludere
reciproc (mutual) a proceselor ntre ele. Dac execuia seciunii critice a
unui proces impune existena unei zone de memorie pe care acel proces s
o monopolizeze pe durata execuiei seciunii sale critice, atunci acea zon
de memorie devine o seciune critic fizic sau resurs critic. De cele
mai multe ori, o astfel de seciune critic fizic este o zon comun de date
partajate de mai multe procese.
Problema excluderii mutuale este legat de proprietatea seciunii
critice de a fi atomic, indivizibil la nivelul de baz al execuiei unui
proces. Aceast atomicitate se poate atinge fie valorificnd facilitile
primitivelor limbajului de programare gazd, fie prin mecanisme controlate
direct de programator (din aceast a doua categorie fac parte algoritmii
specifici scrii pentru rezolvarea anumitor probleme concurente).
Problema excluderii mutuale apare deoarece n fiecare moment cel
mult un proces poate s se afle n seciunea lui critic. Excluderea mutual
este o prim form de sincronizare, ca o alternativ pentru sincronizarea pe
condiie.

20
Problema excluderii mutuale este una central n contextul programrii
paralele. Rezolvarea problemei excluderii mutuale depinde de ndeplinirea
a trei cerine fundamentale, i anume:
1) excluderea reciproc propriu-zis, care const n faptul c la
fiecare moment cel mult un proces se afl n seciunea sa critic;
2) competiia constructiv, neantagonist, care se exprim astfel:
dac niciun proces nu este n seciunea critic i dac exist procese
care doresc s intre n seciunile lor critice, atunci unul dintre acestea
va intra efectiv;
3) conexiunea liber ntre procese, care se exprim astfel: dac un
proces ntrzie n seciunea sa necritic, atunci aceast situaie nu
trebuie s mpiedice alt proces s intre n seciunea sa critic (dac
dorete acest lucru).
ndeplinirea condiiei a doua asigur c procesele nu se mpiedic unul
pe altul s intre n seciunea critic i nici nu se invit la nesfrit unul pe
altul s intre n seciunile critice. n condiia a treia, dac ntrzierea este
definitiv, aceasta nu trebuie s blocheze ntregul sistem (cu alte cuvinte,
blocarea local nu trebuie s antreneze automat blocarea global a
sistemului).
Excluderea reciproc (mutual) fiind un concept central, esenial al
concurenei, n paragrafele urmtoare vom reveni asupra rezolvrii acestei
probleme la diferite nivele de abstractizare i folosind diferite primitive.
Principalele abordri sunt:
1) folosind suportul hardware al sistemului gazd: rezolvarea
excluderii mutuale se face cu task-uri; aceste primitive testeaz i
seteaz variabile booleene (o astfel de variabil este asociat unei
resurse critice); un task nu este o operaie atomic;
2) folosind suportul software: rezolvarea excluderii mutuale se
bazeaz pe facilitile oferite de limbajele de programare pentru
comunicarea i sincronizarea proceselor executate nesecvenial
(paralel sau concurent).

21
CAPITOLUL 3. MODELUL MPI DE PROGRAMARE
PARALEL PE SISTEME DE CALCUL CU
MEMORIE DISTRIBUIT
Obiective
S cunoasc noiunile de standard MPI, programe MPI, s poat
paraleliza un cod secvenial al unui program n limbajul C++;
S cunoasc sintaxa i modul de utilizare a funciilor MPI;
S elaboreze programe MPI n limbajul C++ pentru implementarea
diferitor algoritmi paraleli pe sisteme de calcul paralel cu memorie
distribuit;
S poat utiliza comenzile sistemului ROCKS pentru lansarea n execuie
i monitorizarea programelor MPI pe sisteme paralele de tip cluster.

3.1 Generaliti
MPI este un standard pentru comunicarea prin mesaje, elaborat de MPI
Forum. n definirea lui au fost utilizate caracteristicile cele mai importante
ale unor sisteme anterioare, bazate pe comunicaia de mesaje. A fost
valorificat experiena de la IBM, Intel (NX/2) Express, nCUBE (Vertex),
PARMACS, Zipcode, Chimp, PVM, Chameleon, PICL. MPI are la baz
modelul proceselor comunicante prin mesaje: un calcul este o colecie de
procese secveniale care coopereaz prin comunicare de mesaje. MPI este o
bibliotec, nu un limbaj. El specific convenii de apel pentru mai multe
limbaje de programare: C, C++, FORTRAN.77, FORTRAN90, Java.
MPI a ajuns la versiunea 3, trecnd succesiv prin versiunile:
MPI 1 (1993), un prim document, orientat pe comunicarea punct la
punct;
MPI 1.0 (iunie 1994) este versiunea final, adoptat ca standard;
include comunicarea punct la punct i comunicarea colectiv;
MPI 1.1 (1995) conine corecii i extensii ale documentului iniial
din 1994, modificrile fiind univoce;
MPI 1.2 (1997) conine extensii i clarificri pentru MPI 1.1;
MPI 2 (1997) include funcionaliti noi:
procese dinamice;
comunicarea one-sided;
operaii de I/E (utilizarea fiierelor) paralele.

22
Obiectivele MPI:
proiectarea unei interfee de programare a aplicaiilor;
comunicare eficient;
s fie utilizabil n medii eterogene;
portabilitate;
interfaa de comunicare s fie sigur (erorile tratate dedesubt);
apropiere de practici curente (PVM, NX, Express, p4;
semantica interfeei s fie independent de limbaj.
Astfel, MPI este o bibliotec de funcii, care permite realizarea
interaciunii proceselor paralele prin mecanismul de transmitere a
mesajelor. Este o bibliotec complex, format din aproximativ 130 de
funcii care includ:
funcii de iniializare i nchidere (lichidare) a proceselor MPI;
funcii care implementeaz operaiunile de comunicare de tip
proces-proces;
funcii care implementeaz operaiunile colective;
funcii pentru lucrul cu grupuri de procese i comunicatori;
funcii pentru lucrul cu structuri de date;
funcia care implementeaz generarea dinamic a proceselor;
funcii care implementeaz comunicri unidirecionale ntre
procese (accesul la distan a memoriei);
funcii de lucru cu fiierele.
Fiecare dintre funciile MPI este caracterizat prin metoda de realizare
(executare):
funcie local se realizeaz n cadrul procesului care face apel la
ea, la finalizarea ei nu este nevoie de transmitere de mesaje;
funcie nelocal pentru finalizarea ei este necesar utilizarea
unor proceduri MPI, executate de alte procese;
funcie global procedura trebuie s fie executat de toate
procesele grupului;
funcie de blocare;
funcie de non-blocare.
n tabelul de mai jos se arat corespondena dintre tipurile predefinite
de date MPI i tipurile de date n limbajul de programare.
Tip de date MPI Tip de date C
MPI_CHAR signed char

23
MPI_SHORT signed short int
MPI_INT signed int
MPI_LONG signed long int
MPI_UNSIGNED_CHAR unsigned char
MPI_UNSIGNED_SHORT unsigned short int
MPI_UNSIGNED unsigned int
MPI_UNSIGNED_LONG unsigned long int
MPI_FLOAT float
MPI_DOUBLE double
MPI_LONG_DOUBLE long double
MPI_BYTE
MPI_PACKED

3.2 Funciile MPI de baz


Fiecare program MPI trebuie s conin un apel al operaiei
MPI_Init. Rolul acesteia este de a iniializa mediul n care programul
va fi executat. Ea trebuie executat o singur dat, naintea altor operaii
MPI. Pentru a evita execuia sa de mai multe ori (i deci provocarea unei
erori), MPI ofer posibilitatea verificrii dac MPI_Init a fost sau nu
executat. Acest lucru este fcut prin funcia MPI_Iniatialized. n
limbajul C funcia are urmtorul prototip:
int MPI_Initialized(int *flag);
unde
OUT flag este true dac MPI_Init a fost deja executat.
Aceasta este, de altfel, singura operaie ce poate fi executat nainte de
MPI_Init.

Funcia MPI_Init
Forma general a funciei este
int MPI_Init(int *argc, char ***argv);
Funcia de iniializare accept ca argumente argc i argv,
argumente ale funciei main, a cror utilizare nu este fixat de standard i
depinde de implementare. Ca rezultat al executrii acestei funcii se creeaz

24
un grup de procese, n care se includ toate procesele generate de utilitarul
mpirun, i se creeaz pentru acest grup un mediu virtual de comunicare
descris de comunicatorul cu numele MPI_COMM_WORLD. Procesele din
grup sunt numerotate de la 0 la groupsize-1, unde groupsize este egal cu
numrul de procese din grup. La fel se creeaz comunicatorul cu numele
MPI_COMM_SELF, care descrie mediul de comunicare pentru fiecare
proces n parte.
Perechea funciei de iniializare este MPI_Finalize, care trebuie
executat de fiecare proces, pentru a nchide mediul MPI. Forma acestei
operaii este urmtoarea:
int MPI_Finalize(void).
Utilizatorul trebuie s se asigure c toate comunicaiile n curs de
desfurare s-au terminat nainte de apelul operaiei de finalizare. Dup
MPI_Finalize, nici o alt operaie MPI nu mai poate fi executat (nici
mcar una de iniializare).

Funcia MPI_Comm_size
n limbajul C funcia are urmtorul prototip:
int MPI_Comm_size(MPI_Comm comm, int *size),
unde
IN comm nume comunicator,
OUT size numrul de procese ale comunicatorului comm.
Funcia returneaz numrul de procese MPI ale mediului de
comunicare cu numele comm. Funcia este local.

Funcia MPI_Comm_rank
n limbajul C funcia are urmtorul prototip:
int MPI_Comm_rank(MPI_Comm comm, int *rank),
unde
IN comm nume comunicator,
OUT rank identificatorul procesului din comunicatorul
comm.
Un proces poate afla poziia sa n grupul asociat unui comunicator prin
apelul funciei MPI_Comm_rank. Funcia returneaz un numr din
diapazonul 0,..,size-1, care semnific identificatorul procesului MPI
care a executat aceast funcie. Astfel proceselor li se atribuie un numr n
funcie de ordinea n care a fost executat funcia. Aceast funcie este
25
local.
Vom exemplifica utilizarea funciilor MPI descrise mai sus.
Exemplul 3.2.1. S se elaboreze un program MPI n care fiecare
proces tiprete rankul su i numele nodului unde se execut.
Mai jos este prezentat codul programului n limbajul C++ n care se
realizeaz condiiile enunate n exemplu 3.2.1.
#include <stdio.h> MPI_Get_processor_name(processor_name
#include <stdlib.h> , &namelen);
#include "mpi.h" if (local_rank == 0) {
int main(int argc,char *argv[]) printf("==Au fost generate %s MPI processes
{ pe nodul %s ===\n",
int size,rank,namelen; getenv("OMPI_COMM_WORLD_LOCA
int local_rank = L_SIZE"), processor_name); }
atoi(getenv("OMPI_COMM_WORLD_L MPI_Barrier(MPI_COMM_WORLD);
OCAL_RANK")); if (rank ==0)
char printf("==Procesele comunicatorului
processor_name[MPI_MAX_PROCESSOR MPI_COMM_WORLD au fost
_NAME]; 'distribuite' pe noduri astfel: \n");
MPI_Status status; MPI_Barrier(MPI_COMM_WORLD);
MPI_Init(&argc,&argv); printf("Hello, I am the process number %d
MPI_Comm_size(MPI_COMM_WORLD, (local runk %d) on the compute
&size); hostname %s,from total number of
MPI_Comm_rank(MPI_COMM_WORLD, process %d\n",rank,
&rank); local_rank,processor_name, size);
if (rank ==0) MPI_Finalize();
printf("\n=====REZULTATUL PROGRAMULUI return 0;
'%s'\n", argv[0]); }
MPI_Barrier(MPI_COMM_WORLD);

Rezultatele posibile ale executrii programului:


[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o Exemplu_3_2_1.exe Exemplu_3_2_1.cpp
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpirun -n 7 -machinefile ~/nodes
Exemplu_3_2_1.exe
=====REZULTATUL PROGRAMULUI 'Exemplu_3_2_1.exe'
==Au fost generate 4 MPI processes pe nodul compute-0-0.local ===
==Au fost generate 3 MPI processes pe nodul compute-0-1.local ===
==Procesele comunicatorului MPI_COMM_WORLD au fost 'distribuite' pe noduri astfel:
Hello, I am the process number 0 (local runk 0) on the compute hostname compute-0-0.local , from
total number of process 7
Hello, I am the process number 3 (local runk 3) on the compute hostname compute-0-0.local , from
total number of process 7
Hello, I am the process number 2 (local runk 2) on the compute hostname compute-0-0.local , from
total number of process 7
Hello, I am the process number 4 (local runk 0) on the compute hostname compute-0-1.local , from
total number of process 7
Hello, I am the process number 5 (local runk 1) on the compute hostname compute-0-1.local , from
total number of process 7

26
Hello, I am the process number 1 (local runk 1) on the compute hostname compute-0-0.local , from
total number of process 7
Hello, I am the process number 6 (local runk 2) on the compute hostname compute-0-1.local , from
total number of process 7
[Hancu_B_S@hpc]$

3.3 Comunicarea de tip proces-proces (punct la punct)

3.3.1 Operaii de baz


Operaiile de baz pentru comunicarea punct la punct sunt send i
receive. Modalitile de transmitere a mesajelor pot fi explicate prin
urmtoarele desene:

Comunicaie
Comunica blocant prin buffer
i e b l o c a n t p r i n b u f f e r

Procesor surs Procesor destinaie

Operaie Mesaj buffer Mesaj Operaie


apel send

blocat

t
end send

apel recv

end recv

Comunicaie non-blocant sincron

Procesor surs Procesor destinaie


Operaie Mesaj Mesaj Operaie

apel send
end send

t
alte operaii apel recv

memorie liber
end recv

Considerm mai nti operaia de transmitere de mesaje, n forma sa


uzual:
27
send(adresa,lungime,destinatie,tip)
unde
adresa identific nceputul unei zone de memorie unde se afl mesajul
de transmis,
lungime este lungimea n octei a mesajului,
destinatie este identificatorul procesului cruia i se trimite mesajul
(uzual un numr ntreg),
tip(flag) este un ntreg ne-negativ care restricioneaz recepia
mesajului. Acest argument permite programatorului s rearanjeze mesajele
n ordine, chiar dac ele nu sosesc n secvena dorit. Acest set de parametri
este un bun compromis ntre ceea ce utilizatorul dorete i ceea ce sistemul
poate s ofere: transferul eficient al unei zone contigue de memorie de la un
proces la altul. n particular, sistemul ofer mecanismele de pstrare n
coad a mesajelor, astfel c o operaie de recepie
recv(adresa,maxlung, sursa,tip,actlung) se execut cu
succes doar dac mesajul are tipul corect. Mesajele care nu corespund
ateapt n coad. n cele mai multe sisteme, sursa este un argument de
ieire, care indic originea mesajului.

3.3.2 Funcii MPI pentru transmiterea mesajelor


Funcia MPI_Send
n limbajul C aceast funcie are urmtorul prototip, valorile returnate
reprezentnd coduri de eroare:
int MPI_Send(void *buf,int count, MPI_Datatype
datatype,int dest,int tag, MPI_Comm comm),
unde parametrii sunt:
IN buf adresa iniial a tamponului surs;
IN count numrul de elemente (ntreg ne-negativ);
IN datatype tipul fiecrui element;
IN dest numrul de ordine al destinatarului (ntreg);
IN tag tipul mesajului (ntreg);
IN comm comunicatorul implicat.
Astfel, procesul din mediul de comunicare comm, care execut funcia
MPI_Send, expediaz procesului dest, count date de tip datatype
din memoria sa ncepnd cu adresa buf. Acestor date li se atribuie
eticheta tag.
28
Funcia MPI_Recv
Prototipul acestei funcii n limbajul C este
int MPI_Recv(void *buf,int count, MPI_Datatype
datatype,int source,int tag,MPI_Comm comm,
MPI_Status *status),
unde parametrii sunt:
OUT buf adresa iniial a tamponului destinatar;
IN count numrul de elemente (ntreg ne-negativ);
IN datatype tipul fiecrui element;
IN source numrul de ordine al sursei (ntreg);
IN tag tipul mesajului (ntreg);
IN comm comunicatorul implicat;
OUT status starea, element (structur) ce indic sursa, tipul i
contorul mesajului efectiv primit.
Astfel, procesul din mediul de comunicare comm, care execut funcia
MPI_Recv, recepioneaz de la procesul source, count date de tip
datatype care au eticheta tag i le salveaz n memoria sa ncepnd cu
adresa buf.
Operaia send este blocant. Ea nu red controlul apelantului pn
cnd mesajul nu a fost preluat din tamponul surs, acesta din urm putnd
fi reutilizat de transmitor. Mesajul poate fi copiat direct n tamponul
destinatar (dac se execut o operaie recv) sau poate fi salvat ntr-un
tampon temporar al sistemului. Memorarea temporar a mesajului
decupleaz operaiile send i recv, dar este consumatoare de resurse.
Alegerea unei variante aparine implementatorului MPI. i operaia recv
este blocant: controlul este redat programului apelant doar cnd mesajul a
fost recepionat.

Funcia MPI_Sendrecv
n situaiile n care se dorete realizarea unui schimb reciproc de date
ntre procese, mai sigur este de a utiliza o funcie combinat
MPI_Sendrecv.
Prototipul acestei funcii n limbajul C este
int MPI_Sendrecv(void *sendbuf, int sendcount,
MPI_Datatype sendtype, int dest, int
29
sendtag, void *recvbuf, int recvcount,
MPI_Datatype recvtype, int source,
MPI_Datatype recvtag, MPI_Comm comm,
MPI_Status *status),
unde parametrii sunt:
IN sendbuf adresa iniial a tamponului surs;
IN sendcount numrul de elemente transmise (ntreg ne-
negativ);
IN sendtype tipul fiecrui element trimis;
IN dest numrul de ordine al destinatarului (ntreg);
IN sendtag identificatorul mesajului trimis;
OUT recvbuf adresa iniial a tamponului destinatar;
IN recvcount numrul maximal de elemente primite;
IN recvtype tipul fiecrui element primit;
IN source numrul de ordine al sursei (ntreg);
IN recvtag identificatorul mesajului primit;
IN comm comunicatorul implicat;
OUT status starea, element (structura) ce indic sursa, tipul
i contorul mesajului efectiv primit.
Exist i alte funcii MPI care permit programatorului s controleze
modul de comunicare de tip proces-proces. n tabelul de mai jos prezentm
aceste funcii.
Funciile de transmitere a mesajelor de tip proces-proces
Modul de transmitere Cu blocare Cu non-blocare
Transmitere standart MPI_Send MPI_Isend
Transmitere sincron MPI_Ssend MPI_Issend
Transmitere prin tampon MPI_Bsend MPI_Ibsend
Transmitere coordonat MPI_Rsend MPI_Irsend
Recepionare mesaje MPI_Recv MPI_Irecv
Vom ilustra utilizarea funciilor MPI_Send i MPI_Recv prin
urmtorul exemplu.
Exemplul 3.3.1 S se elaboreze un program MPI n limbajul C++ n
care se realizeaz transmiterea mesajelor pe cerc ncepnd cu procesul
30
ncep. Valoarea variabilei ncep este iniializat de toate procesele i
se afl n diapazonul 0,,size-1.
Mai jos este prezentat codul programului n care se realizeaz
condiiile enunate n exemplul 3.3.1.
#include<stdio.h> if(rank==incep)
#include <iostream> {
#include<mpi.h> MPI_Send(&rank,1,MPI_INT, (rank + 1) %
using namespace std; size, 10, MPI_COMM_WORLD);
int main(int argc,char *argv[]) MPI_Recv(&t,1,MPI_INT, (rank+size-1) %
{ size,10,MPI_COMM_WORLD,&status);
int size,rank,t,namelen; }
int incep=8; else
char {
processor_name[MPI_MAX_PROCESS MPI_Recv(&t,1,MPI_INT, (rank+size-1)%size,
OR_NAME]; 10,MPI_COMM_WORLD,&status);
MPI_Status status; MPI_Send(&rank,1,MPI_INT,(rank+1)%size,1
MPI_Init(&argc,&argv); 0,MPI_COMM_WORLD);
MPI_Comm_size(MPI_COMM_WORLD,&siz }
e); printf("Procesul cu rancul %d al nodului '%s'
MPI_Comm_rank(MPI_COMM_WORLD, a primit valoarea %d de la procesul cu
&rank); rancul %d\n", rank, processor_name,
MPI_Get_processor_name(processor_name t, t);
, MPI_Finalize();
&namelen); return 0;
if (rank ==0) }
printf("\n=====REZULTATUL PROGRAMULUI
'%s' \n",argv[0]);

Rezultatele posibile ale executrii programului:


[Hancu_B_S@hpc Finale]$ /opt/openmpi/bin/mpiCC -o Exemplu_3_3_1.exe
Exemplu_3_3_1.cpp
[Hancu_B_S@hpc Finale]$ /opt/openmpi/bin/mpirun -n 9 -machinefile ~/nodes
Exemplu_3_3_1.exe

=====REZULTATUL PROGRAMULUI ' Exemplu_3_3_1.exe'


Procesul cu rancul 0 al nodului 'compute-0-0.local' a primit valoarea 8 de la procesul cu rancul 8
Procesul cu rancul 1 al nodului 'compute-0-0.local' a primit valoarea 0 de la procesul cu rancul 0
Procesul cu rancul 2 al nodului 'compute-0-0.local' a primit valoarea 1 de la procesul cu rancul 1
Procesul cu rancul 3 al nodului 'compute-0-0.local' a primit valoarea 2 de la procesul cu rancul 2
Procesul cu rancul 4 al nodului 'compute-0-1.local' a primit valoarea 3 de la procesul cu rancul 3
Procesul cu rancul 8 al nodului 'compute-0-2.local' a primit valoarea 7 de la procesul cu rancul 7
Procesul cu rancul 5 al nodului 'compute-0-1.local' a primit valoarea 4 de la procesul cu rancul 4
Procesul cu rancul 6 al nodului 'compute-0-1.local' a primit valoarea 5 de la procesul cu rancul 5
Procesul cu rancul 7 al nodului 'compute-0-1.local' a primit valoarea 6 de la procesul cu rancul 6
[Hancu_B_S@hpc Finale]$

31
3.3.3 Exerciii
1. S se elaboreze i s se execute pe clusterul USM un program MPI n
limbajul C++ n care se realizeaz transmiterea mesajelor pe cerc n
direcia acelor de ceas, ncepnd cu procesul ncep, ntre procesele cu
rankul par. Valoarea variabilei ncep este iniializat de toate
procesele i se afl n diapazonul 0,,size-1.
2. S se elaboreze i s se execute pe clusterul USM un program MPI n
limbajul C++ n care se realizeaz transmiterea mesajelor pe cerc n
direcia invers acelor de ceas, ncepnd cu procesul ncep, ntre
procesele cu rankul impar. Valoarea variabilei ncep este iniializat
de toate procesele i se afl n diapazonul 0,,size-1.
3. S se elaboreze i s se execute pe clusterul USM un program MPI n
limbajul C++ folosind funcia MPI_Sendrecv n care se realizeaz
transmiterea mesajelor pe cerc ncepnd cu procesul ncep. Valoarea
variabilei ncep este iniializat de toate procesele i se afl n
diapazonul 0,,size-1. Comparai timpul de execuie al programului
elaborat cu timpul de execuie al programului din Exemplu 3.3.1. Pentru
determinarea timpului de execuie se poate utiliza funcia
MPI_Wtime().
4. S se elaboreze i s se execute pe clusterul USM un program MPI n
limbajul C++ n care procesul cu rankul 0, utiliznd funciile
MPI_Send i MPI_Recv, transmite un mesaj tuturor proceselor din
comunicatorul MPI_COMM_WORLD.
5. S se elaboreze i s se execute pe clusterul USM un program MPI n
limbajul C++ n care procesul cu rankul 0, utiliznd funcia
MPI_Sendrecv, transmite un mesaj tuturor proceselor din
comunicatorul MPI_COMM_WORLD.

3.4 Funciile MPI pentru comunicarea colectiv


Operaiile colective implic un grup de procese. Pentru execuie,
operaia colectiv trebuie apelat de toate procesele, cu argumente
corespondente. Procesele implicate sunt definite de argumentul
comunicator, care precizeaz totodat contextul operaiei. Multe operaii
colective, cum este difuzarea, presupun existena unui proces deosebit de
celelalte, aflat la originea transmiterii sau recepiei. Un astfel de proces se
32
numete rdcin. Anumite argumente sunt specifice rdcinii i sunt
ignorate de celelalte procese, altele sunt comune tuturor.
Operaiile colective se mpart n mai multe categorii:
sincronizare de tip barier a tuturor proceselor grupului;
comunicri colective, n care se includ:
difuzarea;
colectarea datelor de la membrii grupului la rdcin;
distribuirea datelor de la rdcin spre membrii grupului;
combinaii de colectare/distribuire (allgather i alltoall);
calcule colective:
operaii de reducere;
combinaii reducere/distribuire.
Caracteristici comune ale operaiilor colective de transmitere a
mesajelor.
Tipul datelor
Cantitatea de date transmis trebuie s corespund cu cantitatea de
date recepionat. Deci, chiar dac hrile tipurilor difer, semnturile lor
trebuie s coincid.
Sincronizarea
Terminarea unui apel are ca semnificaie faptul c apelantul poate
utiliza tamponul de comunicare, participarea sa n operaia colectiv
ncheindu-se. Asta nu arat c celelalte procese din grup au terminat
operaia. Ca urmare, exceptnd sincronizarea de tip barier, o operaie
colectiv nu poate fi folosit pentru sincronizarea proceselor.
Comunicator
Comunicrile colective pot folosi aceeai comunicatori ca cei punct la
punct. MPI garanteaz diferenierea mesajelor generate prin operaii
colective de cele punct la punct.
Implementare
Rutinele de comunicare colectiv pot fi bazate pe cele punct la punct.
Aceasta sporete portabilitatea fa de implementrile bazate pe rutine ce
exploateaz arhitecturi paralele.
Principala diferen dintre operaiile colective i operaiile de tip punct
la punct este faptul c acestea implic ntotdeauna toate procesele asociate
cu un mediu virtual de comunicare (comunicator). Setul de operaii
colective include:
sincronizarea tuturor proceselor prin bariere (funcia MPI_Barrier);
33
aciuni de comunicare colectiv, care includ:
livrare de informaii de la un proces la toi ceilali membri ai
comunicatorului (funcia MPI_Bcast);
o asamblare (adunare) a masivelor de date distribuite pe o serie de
procese ntr-un singur tablou i pstrarea lui n spaiul de adrese a
unui singur proces (root) (funciile MPI_Gather,
MPI_Gatherv);
o asamblare (adunare) a masivelor de date distribuite pe o serie de
procese ntr-un singur tablou i pstrarea lui n spaiul de adrese al
tuturor proceselor comunicatorului (funciile MPI_Allgather,
MPI_Allgatherv);
divizarea unui masiv i trimiterea fragmentelor sale tuturor
proceselor din comunicatorul indicat (funciile MPI_Scatter,
MPI_Scatterv);
o combinare a operaiilor Scatter/Gather(All-to-All),
fiecare proces mparte datele sale din memoria tampon de
trimitere i distribuie fragmentele de date tuturor proceselor, i,
concomitent, colecteaz fragmentele trimise ntr-un tampon de
recepie (funciile MPI_Alltoall, MPI_Alltoallv);
operaiile globale de calcul asupra datelor din memoria diferitor
procese:
cu pstrarea rezultatelor n spaiul de adrese al unui singur proces
(funcia MPI_Reduce);
cu difuzarea (distribuirea) rezultatelor tuturor proceselor
comunicatorului indicat (funcia MPI_Allreduce);
operaii combinate Reduce/Scatter (funcia
MPI_Reduce_scatter);
operaie de reducere prefix (funcia MPI_Scan).
Toate rutinele de comunicare, cu excepia MPI_Bcast, sunt
disponibile n dou versiuni:
varianta simpl, cnd toate mesajele transmise au aceeai lungime i
ocup zonele adiacente n spaiul de adrese al procesului;
varianta de tip "vector" (funciile cu simbol suplimentar "v" la
sfritul numelui), care ofer mai multe oportuniti pentru organizarea
de comunicaii colective i elimin restriciile inerente din varianta

34
simpl, att n ceea ce privete lungimea blocurilor de date, ct i n
ceea ce privete plasarea datelor n spaiul de adrese al proceselor.
Funcia MPI_Barrier
Realizeaz o sincronizare de tip barier ntr-un grup de procese MPI;
apelul se blocheaz pn cnd toate procesele dintr-un grup ajung la
barier. Este singura funcie colectiv ce asigur sincronizare. Prototipul
acestei funcii n limbajul C este
int MPI_Barrier(MPI_Comm comm)
unde
IN comm comunicatorul implicat.
Aceast funcie, mpreun cu funcia sleep, poate fi utilizat pentru
a sincroniza (a ordona) tiparul astfel: primul tiprete procesul cu rankul 0,
dup care tiprete procesul cu rankul 1, .a.m.d. Codul de program care
realizeaz acest lucru este:
#include <mpi.h>
int main (int argc , char *argv[])
{
int rank,time;
MPI_Init (&argc, &argv);
MPI_Comm_rank (MPI_COMM_WORLD, &rank);
time=2;
sleep(time*rank);
std::cout << "Synchronization point for:" << rank << std::endl ;
MPI_Barrier(MPI_COMM_WORLD) ;
std::cout << "After Synchronization, id:" << rank << std::endl ;
MPI_Finalize();
return 0;
}
Rezultatele posibile ale executrii programului:
[Hancu_B_S@hpc Finale]$ /opt/openmpi/bin/mpiCC -o Sinhronizare.exe Sinhronizare.cpp
[Hancu_B_S@hpc Finale]$ /opt/openmpi/bin/mpirun -n 4 -host compute-0-0,compute-0-1
Sinhronizare.exe
Synchronization point for:0
Synchronization point for:1
Synchronization point for:2
Synchronization point for:3
After Synchronization, id:2
After Synchronization, id:0
After Synchronization, id:3
After Synchronization, id:1

Funcia MPI_Bcast
Trimite un mesaj de la procesul cu rankul root ctre toate celelalte
procese ale comunicatorului. Prototipul acestei funcii n limbajul C este
35
int MPI_Bcast(void* buffer,int count,
MPI_Datatype datatype,int root,MPI_Comm
comm),
unde parametrii sunt:
IN OUT buf adresa iniial a tamponului destinatar;
IN count numrul de elemente (ntreg ne-negativ);
IN datatype tipul fiecrui element;
IN root numrul procesului care trimite datele;
IN comm comunicatorul implicat.
Grafic aceast funcie poate fi interpretat astfel:

buffer
procesul root
count

buffer buffer buffer


toate procesele ... ...
count count count

Vom exemplifica utilizarea funciei MPI_Bcast prin urmtorul


exemplu.
Exemplu 3.4.1 S se realizeze transmiterea mesajelor pe cerc
ncepnd cu procesul ncep. Valoarea variabilei ncep este iniializat
de procesul cu rankul 0.
Mai jos este prezentat codul programului n limbajul C++ n care se
realizeaz cele menionate mai sus.
#include<stdio.h> MPI_Comm_rank(MPI_COMM_WORLD,
#include <iostream> &rank);
#include<mpi.h> MPI_Get_processor_name(processor_name
using namespace std; ,
int main(int argc,char *argv[]) &namelen);
{ if (rank ==0)
int size,rank,t,namelen,incep; printf("\n=====REZULTATUL PROGRAMULUI
char '%s' \n",argv[0]);
processor_name[MPI_MAX_PROCESS while (true) {
OR_NAME]; MPI_Barrier(MPI_COMM_WORLD);
MPI_Status status; if (rank == 0) {
MPI_Init(&argc,&argv); cout << "Introduceti incep (de la 0 la " <<
MPI_Comm_size(MPI_COMM_WORLD,&siz size - 1 << ", sau numar negativ pentru
e); a opri programul): ";
cin >> incep;
36
} MPI_Recv(&t,1,MPI_INT, (rank+size-1) %
MPI_Bcast(&incep, 1, MPI_INT, 0, size,10,MPI_COMM_WORLD,&status);
MPI_COMM_WORLD); }
if (incep < 0) { else{
MPI_Finalize(); MPI_Recv(&t,1,MPI_INT, (rank+size-
return 0; 1)%size,
} 10,MPI_COMM_WORLD,&status);
if (size <= incep) { MPI_Send(&rank,1,MPI_INT,(rank+1)%size,1
if (rank == 0) { 0,MPI_COMM_WORLD);
cout << "Incep trebuie sa fie mai mic }
decit nr de procesoare (" << size - 1 << printf("Procesul cu rancul %d al nodului '%s'
").\n"; a primit valoarea %d de la procesul cu
} rancul %d\n",rank, processor_name, t,
continue; t);
} }
if(rank==incep){ MPI_Finalize();
MPI_Send(&rank,1,MPI_INT, (rank + 1) % return 0; }
size, 10, MPI_COMM_WORLD);

Rezultatele posibile ale executrii programului:


[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o Exemplu_3_4_1.exe Exemplu_3_4_1.cpp
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpirun -n 9 -machinefile ~/nodes Exemplu_3_4_1.exe

=====REZULTATUL PROGRAMULUI 'Exemplu_3_4_1.exe'


Introduceti incep (de la 0 la 8, sau numar negativ pentru a opri programul): 44
Incep trebuie sa fie mai mic decit nr de procesoare (8).
Introduceti incep (de la 0 la 8, sau numar negativ pentru a opri programul): 8
=====REZULTATUL PROGRAMULUI 'HB_Mesage_Ring_Nproc_V0.exe'
Procesul cu rancul 0 al nodului 'compute-0-0.local' a primit valoarea 8 de la procesul cu rancul 8
Procesul cu rancul 1 al nodului 'compute-0-0.local' a primit valoarea 0 de la procesul cu rancul 0
Procesul cu rancul 2 al nodului 'compute-0-0.local' a primit valoarea 1 de la procesul cu rancul 1
Procesul cu rancul 3 al nodului 'compute-0-0.local' a primit valoarea 2 de la procesul cu rancul 2
Procesul cu rancul 4 al nodului 'compute-0-1.local' a primit valoarea 3 de la procesul cu rancul 3
Procesul cu rancul 8 al nodului 'compute-0-2.local' a primit valoarea 7 de la procesul cu rancul 7
Procesul cu rancul 5 al nodului 'compute-0-1.local' a primit valoarea 4 de la procesul cu rancul 4
Procesul cu rancul 6 al nodului 'compute-0-1.local' a primit valoarea 5 de la procesul cu rancul 5
Procesul cu rancul 7 al nodului 'compute-0-1.local' a primit valoarea 6 de la procesul cu rancul 6
[Hancu_B_S@hpc]$

3.4.1 Funciile MPI pentru asamblarea (adunarea) datelor


Funcia MPI_ Gather
Asambleaz (adun) mesaje distincte de la fiecare proces din grup ntr-
un singur proces destinaie. Asamblarea se face n ordinea creterii rankului
procesorului care trimite datele. Adic datele trimise de procesul i din
tamponul su sendbuf se plaseaz n poriunea i din tamponul recvbuf al
procesului root.

37
Prototipul acestei funcii n limbajul C este
int MPI_Gather(void* sendbuf,int sendcount,
MPI_Datatype sendtype,void* recvbuf,int
recvcount,MPI_Datatype recvtype,int root,
MPI_Comm comm),
unde
IN sendbuf adresa iniial a tamponului pentru datele
trimise;
IN sendcount numrul de elemente trimise (ntreg ne-negativ);
IN sendtype tipul fiecrui element trimis;
out recvbuf adresa iniial a tamponului pentru datele
recepionate (este utilizat numai de procesul
root);
IN recvcount numrul de elemente recepionate de la fiecare
proces (este utilizat numai de procesul root);
IN recvtype tipul fiecrui element primit (este utilizat numai
de procesul root);
IN root numrul procesului care recepioneaz datele;
IN comm comunicatorul implicat.
Tipul fiecrui element trimis sendtype trebuie s coincid cu tipul
fiecrui element primit recvtype i numrul de elemente trimise
sendcount trebuie s fie egal cu numrul de elemente recepionate
recvcount.
Grafic aceast funcie poate fi interpretat astfel:
sendbuf sendbuf sendbuf
toate procesele 0 ... i ... n
sendcount sendcount sendcount

recvbuf
procesul root ...
recvcount recvcount recvcount

Funcia MPI_Allgather
Aceast funcie se execut la fel ca funcia MPI_Gather, dar
destinatarii sunt toate procesele grupului. Datele trimise de procesul i din

38
tamponul su sendbuf se plaseaz n poriunea i din tamponul fiecrui
proces. Prototipul acestei funcii n limbajul C este
int MPI_Allgather(void* sendbuf,int
sendcount, MPI_Datatype sendtype,void*
recvbuf,int recvcount,MPI_Datatype
recvtype,MPI_Comm comm),
unde
IN sendbuf adresa iniial a tamponului pentru datele
trimise;
IN sendcount numrul de elemente trimise (ntreg ne-
negativ);
IN sendtype tipul fiecrui element trimis;
OUT recvbuf adresa iniial a tamponului pentru datele
recepionate;
IN recvcount numrul de elemente recepionate de la fiecare
proces;
IN recvtype tipul fiecrui element primit;
IN comm comunicatorul implicat.

Funcia MPI_Gatherv
Se colecteaz blocuri cu un numr diferit de elemente pentru fiecare
proces, deoarece numrul de elemente preluate de la fiecare proces este
setat individual prin intermediul vectorului recvcounts. Aceast funcie
ofer o mai mare flexibilitate n plasarea datelor n memoria procesului
destinatar, prin introducerea unui tablou displs. Prototipul acestei
funcii n limbajul C este
int MPI_Gatherv(void* sendbuf,int sendcount,
MPI_Datatype sendtype,void* rbuf,int
*recvcounts,int *displs,MPI_Datatype
recvtype,int root,MPI_Comm comm)
unde
IN sendbuf adresa iniial a tamponului pentru datele
trimise;
IN sendcount numrul de elemente trimise (ntreg ne-
negativ);
IN sendtype tipul fiecrui element trimis;
39
OUT rbuf adresa iniial a tamponului pentru datele
recepionate (este utilizat numai de procesul
root);
IN recvcounts un vector cu numere ntregi (dimensiunea
este egal cu numrul de procese din grup),
al crui element i determin numrul de
elemente care sunt recepionate de la
procesul cu i (este utilizat numai de procesul
root);
IN displs un vector cu numere ntregi (dimensiunea
este egal cu numrul de procese din grup),
al crui element i determin deplasarea
blocului i de date fa de rbuf (este utilizat
numai de procesul root);
IN recvtype tipul fiecrui element primit (este utilizat
numai de procesul root);
IN root numrul procesului care recepioneaz
datele;
IN comm comunicatorul implicat.

Mesajele sunt plasate n memoria tampon al procesului root, n


conformitate cu rankurile proceselor care trimit datele, i anume datele
transmise de procesul i, sunt plasate n spaiul de adrese al procesului
root, ncepnd cu adresa rbuf +displs [i].
Grafic aceast funcie poate fi interpretat astfel:
sendbuf sendbuf sendbuf
toate procesele ... ...
sendcount sendcount sendcount

rbuf
procesul root ... ...
recvcounts[i]
displ[i]

40
Funcia MPI_ MPI_Allgatherv
Aceast funcie este similar funciei MPI_Gatherv, culegerea
datelor se face de toate procesele din grup. Prototipul acestei funcii n
limbajul C este
int MPI_Allgatherv(void* sendbuf,int sendcount,
MPI_Datatype sendtype,void* rbuf, int
*recvcounts, int *displs, MPI_Datatype
recvtype, MPI_Comm comm)

unde
IN sendbuf adresa iniial a tamponului pentru datele
trimise;
IN sendcount numrul de elemente trimise (ntreg ne-
negativ);
IN sendtype tipul fiecrui element trimis;
OUT rbuf adresa iniial a tamponului pentru datele
recepionate;
IN recvcounts un vector cu numere ntregi (dimensiunea
este egal cu numrul de procese din grup),
al crui element i determin numrul de
elemente care sunt recepionate de la
procesul cu i;
IN displs un vector cu numere ntregi (dimensiunea
este egal cu numrul de procese din grup),
al crui element i determin deplasarea
blocului i de date fa de rbuf;
IN recvtype tipul fiecrui element primit;
IN comm comunicatorul implicat.

3.4.2 Funciile MPI pentru difuzarea (distribuirea) datelor


Funciile MPI prin intermediul crora se poate realiza distribuirea
colectiv a datelor tuturor proceselor din grup sunt: MPI_Scatter i
MPI_Scaterv.

41
Funcia MPI_Scatter

Funcia MPI_Scatter mparte mesajul care se afl pe adresa variabilei


sendbuf a procesului cu rankul root n pri egale de dimensiunea
sendcount i trimite partea i pe adresa variabilei recvbuf a procesului
cu rankul i (inclusiv sie). Procesul root utilizeaz att tamponul
sendbuf ct i recvbuf, celelalte procese ale comunicatorului comm
sunt doar beneficiari, astfel nct parametrii care ncep cu send nu sunt
eseniali. Prototipul acestei funcii n limbajul C este
int MPI_Scatter(void* sendbuf,int sendcount,
MPI_Datatype sendtype,void* recvbuf,int
recvcount,MPI_Datatype recvtype,int root,
MPI_Comm comm),
unde
IN sendbuf adresa iniial a tamponului pentru datele
trimise (distribuite) (este utilizat numai de
procesul root);
IN sendcount numrul de elemente trimise (ntreg ne-
negativ) fiecrui proces;
IN sendtype tipul fiecrui element trimis;
OUT recvbuf adresa iniial a tamponului pentru datele
recepionate;
IN recvcount numrul de elemente recepionate de la fiecare
proces;
IN recvtype tipul fiecrui element recepionat de fiecare
proces;
IN root numrul procesului care distribuie datele;
IN comm comunicatorul implicat.

Tipul fiecrui element trimis sendtype trebuie s coincid cu tipul


fiecrui element primit recvtype i numrul de elemente trimise
sendcount trebuie s fie egal cu numrul de elemente recepionate
recvcount.

Grafic aceast funcie poate fi interpretat astfel:

42
recvbuf recvbuf recvbuf
toate procesele 0 i ... n
recvcount recvcount recvcount

sendbuf
procesul root ...
sendcount sendcount sendcount

Funcia MPI_Scaterv
Aceast funcie este o versiune vectorial a funciei MPI_Scatter i
permite distribuirea pentru fiecare proces a unui numr diferit de elemente.
Adresa de start a elementelor transmise procesului cu rankul i este indicat
n vectorul displs, i numrul de elemente trimise se indic n vectorul
sendcounts. Aceast funcie este inversa funciei MPI_Gatherv.
Prototipul acestei funcii n limbajul C este
int MPI_Scatterv(void* sendbuf, int *sendcounts,
int *displs, MPI_Datatype sendtype, void*
recvbuf, int recvcount, MPI_Datatype
recvtype, int root, MPI_Comm comm)
unde
IN sendbuf adresa iniial a tamponului pentru datele
trimise (este utilizat numai de procesul root);
IN sendcounts un vector cu numere ntregi (dimensiunea este
egal cu numrul de procese din grup), al crui
element i indic numrul de elemente trimise
procesului cu rankul i;
IN displs un vector cu numere ntregi (dimensiunea este
egal cu numrul de procese din grup), al crui
element i determin deplasarea blocului i de
date fa de sendbuf;
IN sendtype tipul fiecrui element trimis;
OUT recvbuf adresa iniial a tamponului pentru datele
recepionate;
IN recvcount numrul de elemente care sunt recepionate;
IN recvtype tipul fiecrui element primit.
IN root numrul procesului care distribuie datele
IN comm comunicatorul implicat
43
Grafic aceast funcie poate fi interpretat astfel:
recvbuf recvbuf recvbuf
toate procesele ... ...
recvcount recvcount recvcount

sendbuf
procesul root ... ...
sendcounts[i]
displ[i]

Vom ilustra utilizarea funciilor MPI_Scatter, MPI_Gather prin


urmtorul exemplu.
Exemplul 3.4.2 S se distribuie liniile unei matrice ptrate
(dimensiunea este egal cu numrul de procese) proceselor din
comunicatorul MPI_COMM_WORLD. Elementele matricei sunt iniializate
de procesul cu rankul 0.
Mai jos este prezentat codul programului n limbajul C++ n care se
realizeaz cele menionate mai sus.
#include<mpi.h> A_Fin=(double*)malloc(numtask*
#include<stdio.h> numtask*sizeof(double));
int main(int argc, char *argv[]) for(int i=0;i<numtask*numtask;i++)
{ A_Init[i]=rand()/1000000000.0;
int numtask,sendcount,reccount,source; printf("Tipar datele initiale\n");
double *A_Init,*A_Fin; for(int i=0;i<numtask;i++)
int i, myrank, root=0; {
MPI_Init(&argc,&argv); printf("\n");
MPI_Comm_rank(MPI_COMM_WORLD, for(int j=0;j<numtask;j++)
&myrank); printf("A_Init[%d,%d]=%5.2f ",i,j,
MPI_Comm_size(MPI_COMM_WORLD, A_Init[i*numtask+j]);
&numtask); }
double Rows[numtask]; printf("\n");
sendcount=numtask; MPI_Barrier(MPI_COMM_WORLD);
reccount=numtask; }
if (myrank ==0) else MPI_Barrier(MPI_COMM_WORLD);
printf("\n=====REZULTATUL PROGRAMULUI MPI_Scatter(A_Init, sendcount,
'%s' \n",argv[0]); MPI_DOUBLE,Rows, reccount,
MPI_Barrier(MPI_COMM_WORLD); MPI_DOUBLE, root,
//Procesul cu rankul root aloca spatiul i MPI_COMM_WORLD);
initializeaza matrice printf("\n");
if(myrank==root) printf("Resultatele f-tiei MPI_Scatter pentru
{ procesul cu rankul %d \n", myrank);
A_Init=(double*)malloc(numtask* for (i=0; i<numtask; ++i)
numtask*sizeof(double)); printf("Rows[%d]=%5.2f ",i, Rows[i]);
printf("\n");
44
MPI_Barrier(MPI_COMM_WORLD); for(int j=0;j<numtask;j++)
MPI_Gather(Rows, sendcount, printf("A_Fin[%d,%d]=%5.2f ",i,j,
MPI_DOUBLE, A_Fin, reccount, A_Fin[i*numtask+j]);
MPI_DOUBLE, root, }
MPI_COMM_WORLD); printf("\n");
if(myrank==root){ MPI_Barrier(MPI_COMM_WORLD);
printf("\n"); }
printf("Resultatele f-tiei MPI_Gather "); else MPI_Barrier(MPI_COMM_WORLD);
for(int i=0;i<numtask;i++) MPI_Finalize();
{ return 0;
printf("\n"); }

Rezultatele posibile ale executrii programului:


[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o Exemplu_3_4_2.exe Exemplu_3_4_2.cpp
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpirun -n 5 -machinefile ~/nodes Exemplu_3_4_2.exe
=====REZULTATUL PROGRAMULUI ' Exemplu_3_4_2.exe'
Tipar datele initiale
A_Init[0,0]= 1.80 A_Init[0,1]= 0.85 A_Init[0,2]= 1.68 A_Init[0,3]= 1.71 A_Init[0,4]= 1.96
A_Init[1,0]= 0.42 A_Init[1,1]= 0.72 A_Init[1,2]= 1.65 A_Init[1,3]= 0.60 A_Init[1,4]= 1.19
A_Init[2,0]= 1.03 A_Init[2,1]= 1.35 A_Init[2,2]= 0.78 A_Init[2,3]= 1.10 A_Init[2,4]= 2.04
A_Init[3,0]= 1.97 A_Init[3,1]= 1.37 A_Init[3,2]= 1.54 A_Init[3,3]= 0.30 A_Init[3,4]= 1.30
A_Init[4,0]= 0.04 A_Init[4,1]= 0.52 A_Init[4,2]= 0.29 A_Init[4,3]= 1.73 A_Init[4,4]= 0.34
Resultatele f-tiei MPI_Scatter pentru procesul cu rankul 0
Rows[0]= 1.80 Rows[1]= 0.85 Rows[2]= 1.68 Rows[3]= 1.71 Rows[4]= 1.96
Resultatele f-tiei MPI_Scatter pentru procesul cu rankul 1
Resultatele f-tiei MPI_Scatter pentru procesul cu rankul 4
Resultatele f-tiei MPI_Scatter pentru procesul cu rankul 3
Rows[0]= 0.04 Rows[1]= 0.52 Rows[2]= 0.29 Rows[3]= 1.73 Rows[4]= 0.34
Rows[0]= 0.42 Rows[1]= 0.72 Rows[2]= 1.65 Rows[3]= 0.60 Rows[4]= 1.19
Rows[0]= 1.97 Rows[1]= 1.37 Rows[2]= 1.54 Rows[3]= 0.30 Rows[4]= 1.30
Resultatele f-tiei MPI_Scatter pentru procesul cu rankul 2
Rows[0]= 1.03 Rows[1]= 1.35 Rows[2]= 0.78 Rows[3]= 1.10 Rows[4]= 2.04
Resultatele f-tiei MPI_Gather
A_Fin[0,0]= 1.80 A_Fin[0,1]= 0.85 A_Fin[0,2]= 1.68 A_Fin[0,3]= 1.71 A_Fin[0,4]= 1.96
A_Fin[1,0]= 0.42 A_Fin[1,1]= 0.72 A_Fin[1,2]= 1.65 A_Fin[1,3]= 0.60 A_Fin[1,4]= 1.19
A_Fin[2,0]= 1.03 A_Fin[2,1]= 1.35 A_Fin[2,2]= 0.78 A_Fin[2,3]= 1.10 A_Fin[2,4]= 2.04
A_Fin[3,0]= 1.97 A_Fin[3,1]= 1.37 A_Fin[3,2]= 1.54 A_Fin[3,3]= 0.30 A_Fin[3,4]= 1.30
A_Fin[4,0]= 0.04 A_Fin[4,1]= 0.52 A_Fin[4,2]= 0.29 A_Fin[4,3]= 1.73 A_Fin[4,4]= 0.34
[Hancu_B_S@hpc Finale]$
Vom ilustra utilizarea funciilor MPI_Scatterv, MPI_Gatherv
prin urmtorul exemplu.
Exemplul 3.4.3 S se distribuie liniile unei matrice de dimensiuni
arbitrare proceselor din comunicatorul MPI_COMM_WORLD. Elementele
matricei sunt iniializate de procesul cu rankul 0. Fiecare proces MPI

recepioneaz cel puin = linii, unde n numrul de linii n
matricea iniial, size numrul de procese generate, partea ntreag.

45
Mai jos este prezentat codul programului n limbajul C++ n care se
realizeaz cele menionate n enunul exemplului 3.4.3.
#include<mpi.h> MPI_Bcast(&cols, 1, MPI_INT, root,
#include<stdio.h> MPI_COMM_WORLD);
#include <iostream> int rinduriPeProces = rows / size;
using namespace std; int rinduriRamase = rows % size;
int main(int argc, char *argv[]) int deplasarea = 0;
{ for (int i = 0; i < size; ++i)
int size,reccount, source; {
double *A_Init,*A_Fin; displs[i] = deplasarea;
int myrank, root=0; if (i < rinduriRamase)
MPI_Init(&argc,&argv); sendcounts[i] = (rinduriPeProces + 1) * cols;
MPI_Comm_rank(MPI_COMM_WORLD, else
&myrank); sendcounts[i] = rinduriPeProces * cols;
MPI_Comm_size(MPI_COMM_WORLD, deplasarea += sendcounts[i];
&size); }
int sendcounts[size], displs[size]; reccount = sendcounts[myrank];
int rows, cols; Rows = new double[reccount];
double *Rows; MPI_Scatterv(A_Init, sendcounts, displs,
if (myrank ==0) MPI_DOUBLE, Rows, reccount,
printf("\n=====REZULTATUL PROGRAMULUI MPI_DOUBLE, root,
'%s' \n",argv[0]); MPI_COMM_WORLD);
MPI_Barrier(MPI_COMM_WORLD); printf("\n");
if(myrank==root) printf("Rezultatele f-tiei MPI_Scatterv
{ pentru procesul cu rankul %d \n",
cout << "Introduceti numarul de rinduri: "; myrank);
cin >> rows; for (int i=0; i<reccount; ++i)
cout << "Introduceti numarul de coloane: "; printf(" Rows[%d]=%5.2f ", i, Rows[i]);
cin >> cols; printf("\n");
A_Init=(double*)malloc(rows*cols*sizeof cout << "\nProcesul " << myrank << " a
(double)); primit " << reccount << " elemente ("
A_Fin=(double*)malloc(rows*cols*sizeof << reccount / cols << " linii)" << endl;
(double)); MPI_Barrier(MPI_COMM_WORLD);
for(int i=0;i<rows*cols;i++) int sendcount = reccount;
A_Init[i]=rand()/1000000000.0; int *recvcounts = sendcounts;
printf("Matricea initiala\n"); MPI_Gatherv(Rows, sendcount,
for(int i=0;i<rows;i++) MPI_DOUBLE, A_Fin, recvcounts,
{ displs, MPI_DOUBLE, root,
printf("\n"); MPI_COMM_WORLD);
for(int j=0;j<cols;j++) if(myrank==root)
printf("A_Init[%d,%d]=%5.2f ",i,j, A_Init[i * {
cols + j]); printf("\n");
} printf("Resultatele f-tiei MPI_Gatherv ");
printf("\n"); for(int i=0;i<rows;i++)
MPI_Barrier(MPI_COMM_WORLD); {
} printf("\n");
else for(int j=0;j<cols;j++)
MPI_Barrier(MPI_COMM_WORLD); printf("A_Fin[%d,%d]=%5.2f ",i,j,
MPI_Bcast(&rows, 1, MPI_INT, root, A_Fin[i*cols+j]);
MPI_COMM_WORLD); }
printf("\n");
46
MPI_Barrier(MPI_COMM_WORLD);
free(A_Init); MPI_Finalize();
free(A_Fin); delete[] Rows;
} return 0;
else }
MPI_Barrier(MPI_COMM_WORLD);

Rezultatele posibile ale executrii programului:


[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o Exemplu_3_4_3.exe Exemplu_3_4_3.cpp
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpirun -n 2 -machinefile ~/nodes Exemplu_3_4_3.exe
=====REZULTATUL PROGRAMULUI Exemplu_3_4_3.exe'
Introduceti numarul de rinduri: 5
Introduceti numarul de coloane: 6
Matricea initiala
A_Init[0,0]= 1.80 A_Init[0,1]= 0.85 A_Init[0,2]= 1.68 A_Init[0,3]= 1.71 A_Init[0,4]= 1.96 A_Init[0,5]=
0.42
A_Init[1,0]= 0.72 A_Init[1,1]= 1.65 A_Init[1,2]= 0.60 A_Init[1,3]= 1.19 A_Init[1,4]= 1.03 A_Init[1,5]=
1.35
A_Init[2,0]= 0.78 A_Init[2,1]= 1.10 A_Init[2,2]= 2.04 A_Init[2,3]= 1.97 A_Init[2,4]= 1.37 A_Init[2,5]=
1.54
A_Init[3,0]= 0.30 A_Init[3,1]= 1.30 A_Init[3,2]= 0.04 A_Init[3,3]= 0.52 A_Init[3,4]= 0.29 A_Init[3,5]=
1.73
A_Init[4,0]= 0.34 A_Init[4,1]= 0.86 A_Init[4,2]= 0.28 A_Init[4,3]= 0.23 A_Init[4,4]= 2.15 A_Init[4,5]=
0.47
Rezultatele f-tiei MPI_Scatterv pentru procesul cu rankul 0
Rows[0]= 1.80 Rows[1]= 0.85 Rows[2]= 1.68 Rows[3]= 1.71 Rows[4]= 1.96 Rows[5]= 0.42 Rows[6]=
0.72 Rows[7]= 1.65 Rows[8]= 0.60 Rows[9]= 1.19 Rows[10]= 1.03 Rows[11]= 1.35 Rows[12]= 0.78
Rows[13]= 1.10 Rows[14]= 2.04 Rows[15]= 1.97 Rows[16]= 1.37 Rows[17]= 1.54
Rezultatele f-tiei MPI_Scatterv pentru procesul cu rankul 1
Rows[0]= 0.30 Rows[1]= 1.30 Rows[2]= 0.04 Rows[3]= 0.52 Rows[4]= 0.29 Rows[5]= 1.73 Rows[6]=
0.34 Rows[7]= 0.86 Rows[8]= 0.28 Rows[9]= 0.23 Rows[10]= 2.15 Rows[11]= 0.47
Procesul 0 a primit 18 elemente (3 linii)
Procesul 1 a primit 12 elemente (2 linii)
Resultatele f-tiei MPI_Gatherv
A_Fin[0,0]= 1.80 A_Fin[0,1]= 0.85 A_Fin[0,2]= 1.68 A_Fin[0,3]= 1.71 A_Fin[0,4]= 1.96 A_Fin[0,5]= 0.42
A_Fin[1,0]= 0.72 A_Fin[1,1]= 1.65 A_Fin[1,2]= 0.60 A_Fin[1,3]= 1.19 A_Fin[1,4]= 1.03 A_Fin[1,5]= 1.35
A_Fin[2,0]= 0.78 A_Fin[2,1]= 1.10 A_Fin[2,2]= 2.04 A_Fin[2,3]= 1.97 A_Fin[2,4]= 1.37 A_Fin[2,5]= 1.54
A_Fin[3,0]= 0.30 A_Fin[3,1]= 1.30 A_Fin[3,2]= 0.04 A_Fin[3,3]= 0.52 A_Fin[3,4]= 0.29 A_Fin[3,5]= 1.73
A_Fin[4,0]= 0.34 A_Fin[4,1]= 0.86 A_Fin[4,2]= 0.28 A_Fin[4,3]= 0.23 A_Fin[4,4]= 2.15 A_Fin[4,5]= 0.47
[Hancu_B_S@hpc]$

3.4.3 Funciile MPI pentru operaii de reducere


n programarea paralel operaiile matematice pe blocuri de date care
sunt distribuite ntre procesoare se numesc operaii globale de reducere. O
operaie de acumulare se mai numete i operaie global de reducere.
Fiecare proces pune la dispoziie un bloc de date, care sunt combinate cu o
operaie binar de reducere; rezultatul acumulat este colectat la procesul

47
root. n MPI, o operaiune global de reducere este reprezentat n
urmtoarele moduri:
meninerea rezultatelor n spaiul de adrese al unui singur proces
(funcia MPI_Reduce);
meninerea rezultatelor n spaiul de adrese al tuturor proceselor
(funcia MPI_Allreduce);
operaia de reducere prefix, care n calitate de rezultat returneaz
un vector al crui component i este rezultatul operaiei de reducere
ale primelor i componente din vectorul distribuit (funcia
MPI_Scan).
Funcia MPI_Reduce se execut astfel. Operaia global de reducere
indicat prin specificarea parametrului op, se realizeaz pe primele
elemente ale tamponului de intrare, iar rezultatul este trimis n primul
element al tamponului de recepionare al procesului root. Acelai lucru se
repet pentru urmtoarele elemente din memoria tampon etc. Prototipul
acestei funcii n limbajul C este:
int MPI_Reduce(void* sendbuf, void* recvbuf, int
count, MPI_Datatype datatype, MPI_Op op, int
root, MPI_Comm comm)
unde
IN sendbuf adresa iniial a tamponului pentru datele de
intrare;
OUT recvbuf adresa iniial a tamponului pentru rezultate (se
utilizeaz numai de procesul root);
IN count numrul de elemente n tamponul de intrare;
IN datatype tipul fiecrui element din tamponul de intrare;
IN op operaia de reducere;
IN root numrul procesului care recepioneaz
rezultatele operaiei de reducere;
IN comm comunicatorul implicat.
Grafic aceast funcie poate fi interpretat astfel:
sendbuf Procesul root
Proces 0 a0 b0 c0 recvbuf
sendbuf reduce a0+a1+a2 b0+b1+b2 c0+c1+c2
Proces 1 a1 b1 c1
sendbuf
Proces 2 a2 b2 c2
48
n acest desen operaia "+" semnific orice operaie admisibil de
reducere. n calitate de operaie de reducere se poate utiliza orice operaie
predefinit sau operaii determinate (construite) de utilizator folosind
funcia MPI_Op_create.
n tabelul de mai jos sunt prezentate operaiile predefinite care pot fi
utilizate n funcia MPI_Reduce.
Nume Operaia Tipuri de date admisibile
MPI_MAX Maximum
MPI_MIN integer, floating point
Minimum
MPI_SUM Suma integer,floating point,
MPI_PROD Produsul complex
MPI_LAND AND
MPI_LOR OR integer, logical
MPI_LXOR excludere OR
MPI_BAND AND
MPI_BOR OR integer, byte
MPI_BXOR excludere OR
MPI_MAXLOC Valoarea maximal i indicele
MPI_MINLOC Valoarea minimal i indicele Tip special de date

n tabelul de mai sus pentru tipuri de date se utilizeaz urmtoarele


notaii:
integer MPI_INT, MPI_LONG, MPI_SHORT,
MPI_UNSIGNED_SHORT, MPI_UNSIGNED,
MPI_UNSIGNED_LONG
floating MPI_FLOAT, MPI_DOUBLE, MPI_REAL,
point MPI_DOUBLE_PRECISION, MPI_LONG_DOUBLE
logical MPI_LOGICAL
complex MPI_COMPLEX
byte MPI_BYTE
Operaiile MAXLOC i MINLOC se execut pe un tip special de date
fiecare element coninnd dou valori: valoarea maximului sau minimului
i indicele elementului. n MPI exist 9 astfel de tipuri predefinite.

49
MPI_FLOAT_INT float and int
MPI_DOUBLE_INT double and int
MPI_LONG_INT long and int
MPI_2INT int and int
MPI_SHORT_INT short and int
MPI_LONG_DOUBLE_INT long double and int
Vom ilustra utilizarea operaiilor globale de reducere MPI_SUM n
baza urmtorului exemplu.
Exemplul 3.4.4 S se calculeze valoarea aproximativ a lui prin
1 4
integrare numeric cu formula = 0 , folosind formula
1+ 2
dreptunghiurilor. Intervalul nchis [0,1] se mparte ntr-un numr de n
subintervale i se nsumeaz ariile dreptunghiurilor avnd ca baz fiecare
subinterval. Pentru execuia algoritmului n paralel, se atribuie, fiecruia
dintre procesele din grup, un anumit numr de subintervale. Cele dou
operaii colective care apar n rezolvare sunt:
difuzarea valorilor lui n, tuturor proceselor;
nsumarea valorilor calculate de procese.
Mai jos este prezentat codul programului n limbajul C++ care
determin valoarea aproximativ a lui .
#include "mpi.h" MPI_Comm_rank(MPI_COMM_WORLD,
#include <stdio.h> &myid);
#include <math.h> MPI_Get_processor_name(processor_name
double f(double a) ,&namelen);
{ n = 0;
return (4.0 / (1.0 + a*a)); while (!done)
} {
int main(int argc, char *argv[]) if (myid == 0)
{ {
int done = 0, n, myid, numprocs, i; printf("===== Rezultatele programului
double PI25DT = '%s' =====\n",argv[0]);
3.141592653589793238462643; printf("Enter the number of intervals:
double mypi, pi, h, sum, x; (0 quits) ");fflush(stdout);
double startwtime, endwtime; scanf("%d",&n);
int namelen; MPI_Barrier(MPI_COMM_WORLD);
char startwtime = MPI_Wtime();
processor_name[MPI_MAX_PROCESS }
OR_ else MPI_Barrier(MPI_COMM_WORLD);
NAME]; MPI_Bcast(&n, 1, MPI_INT, 0,
MPI_Init(&argc,&argv); MPI_COMM_WORLD);
MPI_Comm_size(MPI_COMM_WORLD, if (n == 0)
&numprocs); done = 1;

50
else MPI_Reduce(&mypi, &pi, 1,
{ MPI_DOUBLE, MPI_SUM, 0,
h = 1.0 / (double) n; MPI_COMM_WORLD);
sum = 0.0; if (myid == 0)
for (i = myid + 1; i <= n; i += {
numprocs) printf("Pi is approximately %.16f,
{ Error is %.16f\n",
x = h * ((double)i - 0.5); pi, fabs(pi - PI25DT));
sum += f(x); endwtime = MPI_Wtime();
} printf("wall clock time = %f\n",
mypi = h * sum; endwtime-startwtime);
fprintf(stderr,"Process %d on %s }
mypi= %.16f\n", myid, }
processor_name, mypi); }
fflush(stderr); MPI_Finalize();
}

Rezultatele posibile ale executrii programului:


[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o Exemplu 3.4.4.exe Exemplu_3_4_4.cpp
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpirun -n 16 -machinefile ~/nodes4 Exemplu 3.4.4.exe
===== Rezultatele programului ' Exemplu_3_4_4.exe' =====
Enter the number of intervals: (0 quits) 100000
Process 1 on compute-0-2.local mypi= 0.1963576657186469
Process 2 on compute-0-2.local mypi= 0.1963564157936524
Process 3 on compute-0-2.local mypi= 0.1963551658561532
Process 4 on compute-0-4.local mypi= 0.1963539159061520
Process 10 on compute-0-6.local mypi= 0.1963464159436181
Process 12 on compute-0-8.local mypi= 0.1963439158561127
Process 0 on compute-0-2.local mypi= 0.1963589156311392
Process 6 on compute-0-4.local mypi= 0.1963514159686426
Process 8 on compute-0-6.local mypi= 0.1963489159811299
Process 15 on compute-0-8.local mypi= 0.1963401656311267
Process 7 on compute-0-4.local mypi= 0.1963501659811359
Process 9 on compute-0-6.local mypi= 0.1963476659686233
Process 14 on compute-0-8.local mypi= 0.1963414157186182
Process 5 on compute-0-4.local mypi= 0.1963526659436477
Process 11 on compute-0-6.local mypi= 0.1963451659061137
Process 13 on compute-0-8.local mypi= 0.1963426657936135
Pi is approximately 3.1415926535981260, Error is 0.0000000000083329
wall clock time = 0.000813
===== Rezultatele programului 'HB_Pi.exe' =====
Enter the number of intervals: (0 quits) 100000000
Process 8 on compute-0-6.local mypi= 0.1963495402243708
Process 15 on compute-0-8.local mypi= 0.1963495314743671
Process 4 on compute-0-4.local mypi= 0.1963495452243360
Process 5 on compute-0-4.local mypi= 0.1963495439743813
Process 3 on compute-0-2.local mypi= 0.1963495464743635
Process 11 on compute-0-6.local mypi= 0.1963495364743647
Process 1 on compute-0-2.local mypi= 0.1963495489743604
Process 13 on compute-0-8.local mypi= 0.1963495339743620
Process 14 on compute-0-8.local mypi= 0.1963495327243415

51
Process 0 on compute-0-2.local mypi= 0.1963495502243742
Process 7 on compute-0-4.local mypi= 0.1963495414743800
Process 2 on compute-0-2.local mypi= 0.1963495477243492
Process 12 on compute-0-8.local mypi= 0.1963495352243697
Process 9 on compute-0-6.local mypi= 0.1963495389743577
Process 6 on compute-0-4.local mypi= 0.1963495427243758
Process 10 on compute-0-6.local mypi= 0.1963495377243211
Pi is approximately 3.1415926535897749, Error is 0.0000000000000182
wall clock time = 0.172357
===== Rezultatele programului 'HB_Pi.exe' =====
Enter the number of intervals: (0 quits) 0
[Hancu_B_S@hpc]$
Vom exemplifica utilizarea operaiilor globale de reducere MPI_MAX
i MPI_MIN n cele ce urmeaz.
Exemplul 3.4.5 S se determine elementele maximale de pe coloanele
unei matrice ptrate (dimensiunea este egal cu numrul de procese).
Elementele matricei sunt iniializate de procesul cu rankul 0. S fie utilizate
funciile MPI_Reduce i operaia MPI_MAX..
Mai jos este prezentat codul programului n limbajul C++ n care se
realizeaz cele menionate n exemplul 3.4.5.
#include<mpi.h> printf("\n");
#include<stdio.h> for(int j=0;j<numtask;j++)
int main(int argc, char *argv[]) printf("A[%d,%d]=%5.2f
{ ",i,j,A[i*numtask+j]);
int numtask,sendcount,reccount,source; }
double *A,*Max_Col; printf("\n");
int i, myrank, root=0; MPI_Barrier(MPI_COMM_WORLD);
MPI_Init(&argc,&argv); }
MPI_Comm_rank(MPI_COMM_WORLD, else MPI_Barrier(MPI_COMM_WORLD);
&myrank); MPI_Scatter(A,sendcount,MPI_DOUBLE,
MPI_Comm_size(MPI_COMM_WORLD, Rows,reccount,MPI_DOUBLE,root,MPI
&numtask); _COMM_WORLD);
double Rows[numtask]; MPI_Reduce(Rows,Max_Col,numtask,
sendcount=numtask; MPI_DOUBLE, MPI_MAX, root,
reccount=numtask; MPI_COMM_WORLD);
if(myrank==root) if (myrank==root) {
{ for(int i=0;i<numtask;i++)
printf("\n=====REZULTATUL PROGRAMULUI {
'%s' \n",argv[0]); printf("\n");
A=(double*)malloc(numtask*numtask*sizeo printf("Elementul maximal de pe
f coloana %d=%5.2f ",i,Max_Col[i]);
(double)); }
Max_Col=(double*)malloc(numtask*sizeof printf("\n");
(double)); MPI_Barrier(MPI_COMM_WORLD); }
for(int i=0;i<numtask*numtask;i++) else MPI_Barrier(MPI_COMM_WORLD);
A[i]=rand()/1000000000.0; MPI_Finalize();
printf("Tipar datele initiale\n"); return 0;
for(int i=0;i<numtask;i++) }
{
52
Rezultatele posibile ale executrii programului:
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o Exemplu _3_4_5.exe Exemplu _3_4_5.cpp
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpirun -n 6 -machinefile ~/nodes4 o Exemplu _3_4_5.exe

=====REZULTATUL PROGRAMULUI ' Exemplu _3_4_5.exe'


Tipar datele initiale

A[0,0]= 1.80 A[0,1]= 0.85 A[0,2]= 1.68 A[0,3]= 1.71 A[0,4]= 1.96 A[0,5]= 0.42
A[1,0]= 0.72 A[1,1]= 1.65 A[1,2]= 0.60 A[1,3]= 1.19 A[1,4]= 1.03 A[1,5]= 1.35
A[2,0]= 0.78 A[2,1]= 1.10 A[2,2]= 2.04 A[2,3]= 1.97 A[2,4]= 1.37 A[2,5]= 1.54
A[3,0]= 0.30 A[3,1]= 1.30 A[3,2]= 0.04 A[3,3]= 0.52 A[3,4]= 0.29 A[3,5]= 1.73
A[4,0]= 0.34 A[4,1]= 0.86 A[4,2]= 0.28 A[4,3]= 0.23 A[4,4]= 2.15 A[4,5]= 0.47
A[5,0]= 1.10 A[5,1]= 1.80 A[5,2]= 1.32 A[5,3]= 0.64 A[5,4]= 1.37 A[5,5]= 1.13

Elementul maximal de pe coloana 0= 1.80


Elementul maximal de pe coloana 1= 1.80
Elementul maximal de pe coloana 2= 2.04
Elementul maximal de pe coloana 3= 1.97
Elementul maximal de pe coloana 4= 2.15
Elementul maximal de pe coloana 5= 1.73
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpirun -n 8 -machinefile ~/nodes4 Exemplu _3_4_5.exe

=====REZULTATUL PROGRAMULUI ' Exemplu _3_4_5.exe'


Tipar datele initiale

A[0,0]= 1.80 A[0,1]= 0.85 A[0,2]= 1.68 A[0,3]= 1.71 A[0,4]= 1.96 A[0,5]= 0.42 A[0,6]= 0.72 A[0,7]= 1.65
A[1,0]= 0.60 A[1,1]= 1.19 A[1,2]= 1.03 A[1,3]= 1.35 A[1,4]= 0.78 A[1,5]= 1.10 A[1,6]= 2.04 A[1,7]= 1.97
A[2,0]= 1.37 A[2,1]= 1.54 A[2,2]= 0.30 A[2,3]= 1.30 A[2,4]= 0.04 A[2,5]= 0.52 A[2,6]= 0.29 A[2,7]= 1.73
A[3,0]= 0.34 A[3,1]= 0.86 A[3,2]= 0.28 A[3,3]= 0.23 A[3,4]= 2.15 A[3,5]= 0.47 A[3,6]= 1.10 A[3,7]= 1.80
A[4,0]= 1.32 A[4,1]= 0.64 A[4,2]= 1.37 A[4,3]= 1.13 A[4,4]= 1.06 A[4,5]= 2.09 A[4,6]= 0.63 A[4,7]= 1.66
A[5,0]= 1.13 A[5,1]= 1.65 A[5,2]= 0.86 A[5,3]= 1.91 A[5,4]= 0.61 A[5,5]= 0.76 A[5,6]= 1.73 A[5,7]= 1.97
A[6,0]= 0.15 A[6,1]= 2.04 A[6,2]= 1.13 A[6,3]= 0.18 A[6,4]= 0.41 A[6,5]= 1.42 A[6,6]= 1.91 A[6,7]= 0.75
A[7,0]= 0.14 A[7,1]= 0.04 A[7,2]= 0.98 A[7,3]= 0.14 A[7,4]= 0.51 A[7,5]= 2.08 A[7,6]= 1.94 A[7,7]= 1.83

Elementul maximal de pe coloana 0= 1.80


Elementul maximal de pe coloana 1= 2.04
Elementul maximal de pe coloana 2= 1.68
Elementul maximal de pe coloana 3= 1.91
Elementul maximal de pe coloana 4= 2.15
Elementul maximal de pe coloana 5= 2.09
Elementul maximal de pe coloana 6= 2.04
Elementul maximal de pe coloana 7= 1.97
[Hancu_B_S@hpc Finale]$

Exemplul 3.4.5a S se determine elementele maximale de pe coloanele


unei matrici patrate (dimensiunea este egala cu numarul de procese).
Liniile matricei sunt initializate de fiecare proces n parte. Procesul cu
rankul 0 n baza acestor linii "construiete" matricea. S fie utilizate
functia MPI_Reduce si operatia MPI_MAX.
Mai jos este prezentat codul programului n limbajul C++ n care se
realizeaz cele menionate n exemplul 3.4.5a.
#include<mpi.h> MPI_Barrier(MPI_COMM_WORLD);
#include<stdio.h> MPI_Gather(Rows, sendcount,
#include<stdlib.h> MPI_DOUBLE, A, reccount,
int main(int argc, char *argv[]) MPI_DOUBLE, root,
{ MPI_COMM_WORLD);
int numtask,sendcount,reccount,source; if(myrank==root){
double *A,*Max_Col; printf("\n");
int i, myrank, root=0; printf("Resultatele f-tiei MPI_Gather ");
MPI_Init(&argc,&argv); for(int i=0;i<numtask;i++)
MPI_Comm_rank(MPI_COMM_WORLD, {
&myrank); printf("\n");
MPI_Comm_size(MPI_COMM_WORLD, for(int j=0;j<numtask;j++)
&numtask); printf("A[%d,%d]=%5.2f ", i,j,
double Rows[numtask]; A[i*numtask+j]);
sendcount=numtask; }
reccount=numtask; printf("\n");
if(myrank==root) MPI_Barrier(MPI_COMM_WORLD);
{ }
printf("\n=====REZULTATUL PROGRAMULUI else MPI_Barrier(MPI_COMM_WORLD);
'%s' \n",argv[0]); MPI_Reduce(Rows,Max_Col,numtask,
A=(double*)malloc(numtask*numtask*sizeo MPI_DOUBLE,MPI_MAX,root,
f(double)); MPI_COMM_WORLD);
Max_Col=(double*)malloc(numtask*sizeof(d if (myrank==root) {
ouble)); for(int i=0;i<numtask;i++)
} {
sleep(myrank); printf("\n");
srand(time(NULL)); printf("Elementul maximal de pe
for(int i=0;i<numtask;i++) coloana %d = %5.2f ",i,Max_Col[i]);
Rows[i]=rand()/1000000000.0; }
printf("Tipar datele initiale ale procesului cu printf("\n");
rankul %d \n",myrank); MPI_Barrier(MPI_COMM_WORLD); }
for(int i=0;i<numtask;i++) else MPI_Barrier(MPI_COMM_WORLD);
{ MPI_Finalize();
printf("Rows[%d]=%5.2f ",i,Rows[i]); return 0;
} }
printf("\n");
Rezultatele posibile ale executrii programului:
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o Exemplu _3_4_5a.exe Exemplu _3_4_5a.cpp
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpirun -n 5 -machinefile ~/nodes6 Exemplu _3_4_5a.exe
=====REZULTATUL PROGRAMULUI 'Exemplu_3_4_5a.exe'
Tipar datele initiale ale procesului cu rankul 0
Rows[0]= 1.98 Rows[1]= 1.97 Rows[2]= 0.42 Rows[3]= 0.49 Rows[4]= 2.04
Tipar datele initiale ale procesului cu rankul 1
Rows[0]= 0.60 Rows[1]= 0.71 Rows[2]= 2.15 Rows[3]= 1.18 Rows[4]= 0.82
Tipar datele initiale ale procesului cu rankul 2
Rows[0]= 1.38 Rows[1]= 1.59 Rows[2]= 1.73 Rows[3]= 0.80 Rows[4]= 1.76
Tipar datele initiale ale procesului cu rankul 3
Rows[0]= 0.00 Rows[1]= 0.34 Rows[2]= 0.24 Rows[3]= 1.49 Rows[4]= 1.63
Tipar datele initiale ale procesului cu rankul 4

54
Rows[0]= 1.84 Rows[1]= 1.22 Rows[2]= 1.96 Rows[3]= 1.10 Rows[4]= 0.40

Resultatele f-tiei MPI_Gather


A[0,0]= 1.98 A[0,1]= 1.97 A[0,2]= 0.42 A[0,3]= 0.49 A[0,4]= 2.04
A[1,0]= 0.60 A[1,1]= 0.71 A[1,2]= 2.15 A[1,3]= 1.18 A[1,4]= 0.82
A[2,0]= 1.38 A[2,1]= 1.59 A[2,2]= 1.73 A[2,3]= 0.80 A[2,4]= 1.76
A[3,0]= 0.00 A[3,1]= 0.34 A[3,2]= 0.24 A[3,3]= 1.49 A[3,4]= 1.63
A[4,0]= 1.84 A[4,1]= 1.22 A[4,2]= 1.96 A[4,3]= 1.10 A[4,4]= 0.40

Elementul maximal de pe coloana 0 = 1.98


Elementul maximal de pe coloana 1 = 1.97
Elementul maximal de pe coloana 2 = 2.15
Elementul maximal de pe coloana 3 = 1.49
Elementul maximal de pe coloana 4 = 2.04
[Hancu_B_S@hpc Finale]$

Exemplul 3.4.6 Utiliznd funcia MPI_Reduce i operaiile


MPI_MAX, MPI_MIN, s se determine elementele maximale de pe liniile i
coloanele unei matrice de dimensiuni arbitrare. Elementele matricei sunt
iniializate de procesul cu rankul 0.
Indicaii: Fiecare proces MPI va executa o singur dat funcia
MPI_Reduce. Pentru aceasta n programul de mai jos a fost elaborat
funcia reduceLines n care se realizeaz urmtoarele. Fie c
dimensiunea matricei A este i procesul cu rankul k, n baza funciei
MPI_Scatterv a recepionat linii, adic
1 +11 +1 +1 +
Atunci procesul k construiete urmtorul vector de lungimea m:
max{1 , +11 , , + 1 } max{ , +1 , , + },
care i este utilizat n funcia MPI_Reduce.
Mai jos este prezentat codul programului n limbajul C++ n care se
realizeaz cele menionate n exemplul 3.4.6.

#include<mpi.h> for (int i = 0; i < size; ++i)


#include<stdio.h> {
#include <iostream> displs[i] = currDispl;
using namespace std; if (i < remainRows)
void calculateSendcountsAndDispls(int rows, sendcounts[i] = (rowsPerProc +
int cols, int size, int sendcounts[], int 1)*cols; else
displs[]) sendcounts[i] = rowsPerProc * cols;
{ currDispl += sendcounts[i];
int rowsPerProc = rows / size; }
int remainRows = rows % size; }
int currDispl = 0;
55
void invertMatrix(double *m, int mRows, int cin >> rows;
mCols, double *rez) cout << "Introduceti nr. de coloane a
{ matricei: ";
for (int i = 0; i < mRows; ++i) cin >> cols;
for (int j = 0; j < mCols; ++j) A=(double*)malloc(rows*cols*sizeof
rez[j * mRows + i] = m[i * mCols + j]; (double));
} for(int i=0;i<rows*cols;i++)
void reduceLines(double* Rows, int A[i]=rand()/1000000000.0;
reccount, int cols, double printf("Tipar datele initiale\n");
myReducedRow[], bool min) for(int i=0;i<rows;i++)
{ {
int myNrOfLines = reccount / cols; printf("\n");
for (int i = 0; i < cols; ++i) for(int j=0;j<cols;j++)
{ printf("A[%d,%d]=%5.2f ",i,j,A[i * cols
double myMaxPerCol_i = Rows[i]; + j]);
for (int j = 1; j < myNrOfLines; ++j) }
{ printf("\n");
if (min)
{ MPI_Barrier(MPI_COMM_WORLD)
if (Rows[j * cols + i]<myMaxPerCol_i) ;
myMaxPerCol_i = Rows[j * cols + i]; }
} else
else MPI_Barrier(MPI_COMM_WORLD);
{ MPI_Bcast(&rows, 1, MPI_INT, root,
if (Rows[j * cols + i] > MPI_COMM_WORLD);
myMaxPerCol_i) MPI_Bcast(&cols, 1, MPI_INT, root,
myMaxPerCol_i = Rows[j * cols + i]; MPI_COMM_WORLD);
} if (rows >= size)
} {
myReducedRow[i] = myMaxPerCol_i; calculateSendcountsAndDispls(rows,
} cols, size, sendcounts, displs);
} }
int main(int argc, char *argv[]) else
{ {
int size,reccount, source; cout << "Introduceti un numar de linii
double *A; >= nr de procese." << endl;
int myrank, root=0; MPI_Finalize();
MPI_Init(&argc,&argv); return 0;
MPI_Comm_rank(MPI_COMM_WORLD, }
&myrank); reccount = sendcounts[myrank];
MPI_Comm_size(MPI_COMM_WORLD, Rows = new double[reccount];
&size); MPI_Scatterv(A, sendcounts, displs,
int sendcounts[size], displs[size]; MPI_DOUBLE, Rows, reccount,
int rows, cols; MPI_DOUBLE, root,
double *Rows; MPI_COMM_WORLD);
if(myrank==root) MPI_Barrier(MPI_COMM_WORLD);
{ if(myrank==root) cout << "Liniile matricei au
printf("\n=====REZULTATUL PROGRAMULUI fost repartizate astfel:" << endl;
'%s' \n",argv[0]); cout << "\nProces " << myrank << " - " <<
cout << "Introduceti nr. de rinduri a reccount / cols << " liniile" << endl;
matricei: "; double myReducedRow[cols];
56
reduceLines(Rows, reccount, cols, return 0;
myReducedRow, false); }
double* maxPerCols; reccount = sendcounts[myrank];
if (myrank == root) delete[] Rows;
maxPerCols = new double[cols]; Rows = new double[reccount];
MPI_Reduce(myReducedRow,maxPerCols, MPI_Scatterv(invMatr, sendcounts, displs,
cols,MPI_DOUBLE,MPI_MAX,root, MPI_DOUBLE, Rows, reccount,
MPI_COMM_WORLD); MPI_DOUBLE, root,
if (myrank == root) MPI_COMM_WORLD);
{ double myReducedCol[rows];
printf("\nValorile de maxim pe coloanele reduceLines(Rows, reccount, rows,
matricii sunt:\n"); myReducedCol, true);
for (int i = 0; i < cols; ++i) double* minPerRows;
printf("Coloana %d - %.2f\n", i, if (myrank == root)
maxPerCols[i]); minPerRows = new double[rows];
delete[] maxPerCols; MPI_Reduce(myReducedCol,minPerRows,
} rows,MPI_DOUBLE,MPI_MIN,root,
double *invMatr; MPI_COMM_WORLD);
if (myrank == root) if (myrank == root)
{ {
invMatr = new double[cols * rows]; printf("\nValorile de minim pe liniile matricii
invertMatrix(A, rows, cols, invMatr); sunt:\n");
} for (int i = 0; i < rows; ++i)
if (cols >= size) printf("Rindul %d - %.2f\n", i,
{ minPerRows[i]);
calculateSendcountsAndDispls(cols, rows, delete[] minPerRows;
size, sendcounts, displs); free(A);
} }
else MPI_Finalize();
{ delete[] Rows;
cout << "Introduceti un numar de coloane return 0;
>= nr de procese." << endl; }
MPI_Finalize();

Rezultatele posibile ale executrii programului:


[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o Exemplu _3_4_6.exe Exemplu _3_4_6.cpp
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpirun -n 3 -machinefile ~/nodes4 Exemplu _3_4_6.exe

=====REZULTATUL PROGRAMULUI Exemplu _3_4_6.exe'


Introduceti nr. de rinduri a matricei: 7
Introduceti nr. de coloane a matricei: 6
Tipar datele initiale
A[0,0]= 1.80 A[0,1]= 0.85 A[0,2]= 1.68 A[0,3]= 1.71 A[0,4]= 1.96 A[0,5]= 0.42
A[1,0]= 0.72 A[1,1]= 1.65 A[1,2]= 0.60 A[1,3]= 1.19 A[1,4]= 1.03 A[1,5]= 1.35
A[2,0]= 0.78 A[2,1]= 1.10 A[2,2]= 2.04 A[2,3]= 1.97 A[2,4]= 1.37 A[2,5]= 1.54
A[3,0]= 0.30 A[3,1]= 1.30 A[3,2]= 0.04 A[3,3]= 0.52 A[3,4]= 0.29 A[3,5]= 1.73
A[4,0]= 0.34 A[4,1]= 0.86 A[4,2]= 0.28 A[4,3]= 0.23 A[4,4]= 2.15 A[4,5]= 0.47
A[5,0]= 1.10 A[5,1]= 1.80 A[5,2]= 1.32 A[5,3]= 0.64 A[5,4]= 1.37 A[5,5]= 1.13
A[6,0]= 1.06 A[6,1]= 2.09 A[6,2]= 0.63 A[6,3]= 1.66 A[6,4]= 1.13 A[6,5]= 1.65
Liniile matricei au fost repartizate astfel:
Proces 2 - 2 liniile
57
Proces 0 - 3 liniile
Proces 1 - 2 liniile
Valorile de maxim pe coloanele matricii sunt:
Coloana 0 - 1.80
Coloana 1 - 2.09
Coloana 2 - 2.04
Coloana 3 - 1.97
Coloana 4 - 2.15
Coloana 5 - 1.73
Valorile de minim pe liniile matricii sunt:
Rindul 0 - 0.42
Rindul 1 - 0.60
Rindul 2 - 0.78
Rindul 3 - 0.04
Rindul 4 - 0.23
Rindul 5 - 0.64
Rindul 6 - 0.63
[Hancu_B_S@hpc Finale]$

Vom ilustra utilizarea operaiilor globale de reducere MPI_MAXLOC


prin exemplul ce urmeaz.
Exemplu 3.4.7 Utiliznd funcia MPI_Reduce i operaiile
MPI_MAXLOC s se determine elementele maximale de pe coloane i
indicele liniei, unei matrice ptrate (dimensiunea este egal cu numrul de
procese). Elementele matricei sunt iniializate de procesul cu rankul 0.
Mai jos este prezentat codul programului n limbajul C++ n care se
realizeaz cele menionate n exemplul 3.4.7.
#include<stdio.h> printf("===== Rezultatele programului '%s'
int main(int argc, char *argv[]) =====\n",argv[0]);
{ A=(double*)malloc(numtask*numtask*sizeo
int numtask,sendcount,reccount,source; f(double));
double *A; for(int i=0;i<numtask*numtask;i++)
int i, myrank, root=1; A[i]=rand()/1000000000.0;
MPI_Init(&argc,&argv); printf("Tipar datele initiale\n");
MPI_Comm_rank(MPI_COMM_WORLD, for(int i=0;i<numtask;i++)
&myrank); {
MPI_Comm_size(MPI_COMM_WORLD, printf("\n");
&numtask); for(int j=0;j<numtask;j++)
double ain[numtask], aout[numtask]; printf("A[%d,%d]=%.2f ",i,j,
int ind[numtask]; A[i*numtask+j]);
struct { }
double val; printf("\n");
int rank; MPI_Barrier(MPI_COMM_WORLD);
} n[numtask], out[numtask]; }
sendcount=numtask; else MPI_Barrier(MPI_COMM_WORLD);
reccount=numtask; MPI_Scatter(A, sendcount,
if(myrank==root) MPI_DOUBLE,ain, reccount,
{
58
MPI_DOUBLE, root, printf("Valorile maximale de pe
MPI_COMM_WORLD); coloane i indicele liniei:\n");
for (i=0; i<numtask; ++i) for (i=0; i<numtask; ++i) {
{ aout[i] = out[i].val;
n[i].val = ain[i]; ind[i] = out[i].rank;
n[i].rank = myrank; printf("Coloana %d, valoarea= %.2f,
} linia= %d\n",i, aout[i],ind[i]); }
MPI_Reduce(n,out,numtask,MPI_DOUBLE_ }
INT, MPI_MAXLOC, root, MPI_Finalize();
MPI_COMM_WORLD); return 0;
if (myrank == root) }
{printf("\n");

Rezultatele posibile ale executrii programului:


[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o Exemplu_3_4_7.exe Exemplu_3_4_7.cpp
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpirun -n 6 -machinefile ~/nodes4 Exemplu_3_4_7.exe
===== Rezultatele programului 'Exemplu_3_4_7.exe' =====
Tipar datele initiale

A[0,0]=1.80 A[0,1]=0.85 A[0,2]=1.68 A[0,3]=1.71 A[0,4]=1.96 A[0,5]=0.42


A[1,0]=0.72 A[1,1]=1.65 A[1,2]=0.60 A[1,3]=1.19 A[1,4]=1.03 A[1,5]=1.35
A[2,0]=0.78 A[2,1]=1.10 A[2,2]=2.04 A[2,3]=1.97 A[2,4]=1.37 A[2,5]=1.54
A[3,0]=0.30 A[3,1]=1.30 A[3,2]=0.04 A[3,3]=0.52 A[3,4]=0.29 A[3,5]=1.73
A[4,0]=0.34 A[4,1]=0.86 A[4,2]=0.28 A[4,3]=0.23 A[4,4]=2.15 A[4,5]=0.47
A[5,0]=1.10 A[5,1]=1.80 A[5,2]=1.32 A[5,3]=0.64 A[5,4]=1.37 A[5,5]=1.13
Valorile maximale de pe coloane i indicele liniei:
Coloana 0, valoarea= 1.80, linia= 0
Coloana 1, valoarea= 1.80, linia= 5
Coloana 2, valoarea= 2.04, linia= 2
Coloana 3, valoarea= 1.97, linia= 2
Coloana 4, valoarea= 2.15, linia= 4
Coloana 5, valoarea= 1.73, linia= 3
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpirun -n 8 -machinefile ~/nodes4 Exemplu_3_4_7.exe
===== Rezultatele programului 'Exemplu_3_4_7.exe' =====
Tipar datele initiale
A[0,0]=1.80 A[0,1]=0.85 A[0,2]=1.68 A[0,3]=1.71 A[0,4]=1.96 A[0,5]=0.42 A[0,6]=0.72 A[0,7]=1.65
A[1,0]=0.60 A[1,1]=1.19 A[1,2]=1.03 A[1,3]=1.35 A[1,4]=0.78 A[1,5]=1.10 A[1,6]=2.04 A[1,7]=1.97
A[2,0]=1.37 A[2,1]=1.54 A[2,2]=0.30 A[2,3]=1.30 A[2,4]=0.04 A[2,5]=0.52 A[2,6]=0.29 A[2,7]=1.73
A[3,0]=0.34 A[3,1]=0.86 A[3,2]=0.28 A[3,3]=0.23 A[3,4]=2.15 A[3,5]=0.47 A[3,6]=1.10 A[3,7]=1.80
A[4,0]=1.32 A[4,1]=0.64 A[4,2]=1.37 A[4,3]=1.13 A[4,4]=1.06 A[4,5]=2.09 A[4,6]=0.63 A[4,7]=1.66
A[5,0]=1.13 A[5,1]=1.65 A[5,2]=0.86 A[5,3]=1.91 A[5,4]=0.61 A[5,5]=0.76 A[5,6]=1.73 A[5,7]=1.97
A[6,0]=0.15 A[6,1]=2.04 A[6,2]=1.13 A[6,3]=0.18 A[6,4]=0.41 A[6,5]=1.42 A[6,6]=1.91 A[6,7]=0.75
A[7,0]=0.14 A[7,1]=0.04 A[7,2]=0.98 A[7,3]=0.14 A[7,4]=0.51 A[7,5]=2.08 A[7,6]=1.94 A[7,7]=1.83
Valorile maximale de pe coloane i indicele liniei:
Coloana 0, valoarea= 1.80, linia= 0
Coloana 1, valoarea= 2.04, linia= 6
Coloana 2, valoarea= 1.68, linia= 0
Coloana 3, valoarea= 1.91, linia= 5
Coloana 4, valoarea= 2.15, linia= 3
Coloana 5, valoarea= 2.09, linia= 4
Coloana 6, valoarea= 2.04, linia= 1
Coloana 7, valoarea= 1.97, linia= 5
59
[Hancu_B_S@hpc Notate_Exemple]$

3.4.4 Exerciii
1. S se elaboreze i s se execute pe clusterul USM un program MPI n
limbajul C++ n care se modeleaz funcia MPI_Gather cu ajutorul
funciilor de transmitere a mesajelor de tip proces-proces.
2. S se elaboreze i s se execute pe clusterul USM un program MPI n
limbajul C++ n care se modeleaz funcia MPI_Allreduce.
3. Utiliznd funcia MPI_Reduce i operaiile MPI_MAXLOC, s se
determine elementele maximale de pe coloanele unei matrice de
dimensiune arbitrar i indicele liniei. Elementele matricei sunt
iniializate de procesul cu rankul 0.
4. Folosind funcia MPI_Op_create, s se creeze operaia MPI cu
numele MPI_ALLMAXLOC care va determina toate elementele
maximale i indicele lor. S se elaboreze i s se execute pe clusterul
USM un program MPI n limbajul C++ n care, folosind operaia
MPI_ALLMAXLOC, s se determine toate elementele maximale i
indicele liniei de pe coloanele unei matrice, matrice ptrate
(dimensiunea este egal cu numrul de procese). Elementele matricei
sunt iniializate de procesul cu rankul 0.
=1,
5. Fie dat o matrice = =1, care se mparte n blocuri de
dimensiunea . S se elaboreze i s se execute pe clusterul
USM un program MPI n limbajul C++ care s realizeze urmtoarele:
a. fiecare proces primete un singur1 bloc al matricei A;
b. fiecare proces atribuie valoarea 0 numai acelor elemente ale
submatricei sale care sunt elementele de pe diagonala principal a
matricei A.
Matricea A este iniializat numai de procesul cu rankul 0.

1
Dimensiunea blocului se alege astfel nct s se poat realiza acest lucru.
60
3.5 Rutine MPI de administrare a comunicatorului i a grupelor

3.5.1 Grupe i comunicatori


O grup este o mulime ordonat de procese. Fiecare proces dintr-un
grup este asociat unui rang ntreg unic. Valorile rangului pornesc de la 0
i merg pn la N 1, unde N este numrul de procese din grup.
n MPI un grup este reprezentat n memoria sistemului ca un obiect. El
este accesibil programatorului numai printr-un handle. Exist dou grupe
prestabilite: MPI_GROUP_EMPTY grupul care nu conine niciun proces
i MPI_GROUP_NUL se returneaz aceast valoare n cazul cnd grupul
nu poate fi creat. Un grup este totdeauna asociat cu un obiect comunicator.
Un comunicator cuprinde un grup de procese care pot comunica ntre ele.
Toate mesajele MPI trebuie s specifice un comunicator. n sensul cel mai
simplu, comunicatorul este o etichet suplimentar care trebuie inclus n
apelurile MPI. Ca i grupele, comunicatorii sunt reprezentai n memoria
sistemului ca obiecte i sunt accesibili programatorului numai prin
handles. De exemplu, un handle pentru comunicatorul care cuprinde
toate task-urile este MPI_COMM_WORLD. Rutinele grupului sunt utilizate
n principal pentru a specifica procesele care trebuie utilizate pentru a
construi un comunicator.
Scopurile principale ale obiectelor grup i comunicator:
Permit organizarea task-urilor, pe baza unor funcii, n grupuri de
task-uri.
Abiliteaz operaiile de comunicare colectiv ntr-un subset de
task-uri ntr-un fel n relaie.
Asigur baza pentru implementarea topologiilor virtuale definite de
utilizator.
Garanteaz sigurana comunicrii.
Restricii i alte consideraii asupra programrii:
Grupurile/comunicatorii sunt obiecte dinamice pot fi create i
distruse n timpul execuiei programului.
Procesele pot fi n mai mult de un grup/comunicator. Ele vor avea
un rang unic n fiecare grup/comunicator.
n MPI comunicarea poate avea loc n cadrul unui grup
(intracomunicare) sau ntre dou grupuri distincte (intercomunicare).

61
Corespunztor, comunicatorii se mpart n dou categorii:
intracomunicatori i intercomunicatori. Un intracomunicator descrie:
un grup de procese;
contexte pentru comunicare punct la punct i colectiv (aceste dou
contexte sunt disjuncte, astfel c un mesaj punct la punct nu se
poate confunda cu un mesaj colectiv, chiar dac au acelai tag);
o topologie virtual (eventual);
alte atribute.
MPI are o serie de operaii pentru manipularea grupurilor, printre
care:.
aflarea grupului asociat unui comunicator;
gsirea numrului de procese dintr-un grup i a rangului procesului
apelant;
compararea a dou grupuri;
reuniunea, intersecia, diferena a dou grupuri;
crearea unui nou grup din altul existent, prin includerea sau
excluderea unor procese;
desfiinarea unui grup.
Funcii similare sunt prevzute i pentru manipularea
intracomunicatorilor:
gsirea numrului de procese sau a rangului procesului apelant;
compararea a doi comunicatori;
duplicarea, crearea unui comunicator;
partiionarea grupului unui comunicator n grupuri disjuncte;
desfiinarea unui comunicator.
Un intercomunicator leag dou grupuri, mpreun cu contextele de
comunicare partajate de cele dou grupuri. Contextele sunt folosite doar
pentru operaii punct la punct, neexistnd comunicare colectiv
intergrupuri. Nu exist topologii virtuale n cazul intercomunicrii. O
intercomunicare are loc ntre un proces iniiator, care aparine unui grup
local, i un proces int, care aparine unui grup distant. Procesul int este
specificat printr-o pereche (comunicator, rang) relativ la grupul distant.
MPI conine peste 40 de rutine relative la grupuri, comunicatori i topologii
virtuale. Mai jos vom descrie o parte din aceste funcii (rutine).

3.5.2 Funciile MPI de gestionare a grupelor de procese

62
Funcia MPI_Group_size
Aceast funcie permite determinarea numrului de procese din grup.
Prototipul acestei funcii n limbajul C++ este
int MPI_Group_size(MPI_Group group, int *size)
unde
IN group numele grupei;
OUT size numrul de procese din grup.

Funcia MPI_Group_rank
Aceast funcie permite determinarea rankului (un identificator
numeric) al proceselor din grup. Prototipul acestei funcii n limbajul C++
este
int MPI_Group_rank(MPI_Group group, int *rank)
unde
IN group numele grupei;
OUT rank numrul procesului din grup.

Dac procesul nu face parte din grupul indicat, atunci se returneaz


valoarea MPI_UNDEFINED.

Funcia MPI_Comm_group
Aceast funcie permite crearea unui grup de procese prin intermediul
unui comunicator. Prototipul acestei funcii n limbajul C++ este
int MPI_Comm_group(MPI_Comm comm, MPI_Group
*group)
unde
IN comm numele comunicatorului;
OUT group numele grupului.
Astfel, se creeaz grupul cu numele group pentru mulimea de procese
care aparin comunicatorului cu numele comm.

Funcile MPI_Comm_union, MPI_Comm_intersection,


MPI_Comm_difference
Urmtoarele trei funcii au aceeai sintax i se utilizeaz pentru
crearea unui grup nou de procese MPI , ca rezultat al unor operaii asupra
mulimilor de procese din dou grupe. Prototipul acestor funcii n limbajul
C++ este

63
int MPI_Group_union(MPI_Group group1, MPI_Group
group2, MPI_Group *newgroup)
int MPI_Group_intersection(MPI_Group group1,
MPI_Group group2, MPI_Group *newgroup)
int MPI_Group_difference(MPI_Group group1,
MPI_Group group2, MPI_Group *newgroup)
unde
IN group1 numele primului grup de procese;
IN group2 numele grupului doi de procese;
OUT newgroup numele grupului nou creat.
Operaiunile sunt definite dup cum urmeaz:
Union creeaz un nou grup din procesele grupei group1 i din acele
procese ale grupei group2 care nu aparin grupei group1 (operaia de
reuniune).
Intersection creeaz un nou grup din procesele grupei group1
care aparin i grupei group2 (operaia de intersecie).
Difference creeaz un nou grup din procesele grupei group1
care nu aparin grupei group2 (operaia de diferen).
Noul grup poate fi i vid, atunci se returneaz MPI_GROUP_EMPTY.

Funcile MPI_Comm_incl, MPI_Comm_excl


Urmtoarele dou funcii sunt de aceeai sintax, dar sunt
complementare. Prototipul acestor funcii n limbajul C++ este
int MPI_Group_incl(MPI_Group group, int n, int
*ranks, MPI_Group *newgroup)
int MPI_Group_excl(MPI_Group group, int n, int
*ranks, MPI_Group *newgroup)
unde
IN group numele grupei existente (printe);
IN n numrul de elemente n vectorul ranks
(care este de fapt egal cu numrul de
procese din grupul nou creat);
IN ranks un vector ce conine rankurile
proceselor;
OUT newgroup numele grupului nou creat.

64
Funcia MPI_Group_incl creeaz un nou grup, care const din
procesele din grupul group, enumerate n vectorul ranks. Procesul cu
numrul i n noul grup newgroup este procesul cu numrul ranks[i]
din grupul existent group.
Funcia MPI_Group_excl creeaz un nou grup din acele procese
ale grupului existent group care nu sunt enumerate n vectorul ranks.
Procesele din grupul newgroup sunt ordonate la fel ca i n grupul iniial
group.
Vom exemplifica rutinele MPI descrise mai sus.
Exemplul 3.5.1 Fie dat un grup printe de procese MPI
numerotate 0,...,size-1. S se elaboreze un program MPI n care se creeaz
un nou grup de k=size/2 procese, alegnd aleator procese din grupul
printe. Fiecare proces din grupul creat tiprete informaia despre sine
n forma: rankul din grupul nou (rankul din grupul printe).
Mai jos este prezentat codul programului n limbajul C++ n care se
realizeaz cele menionate n exemplul 3.5.1
#include<stdio.h> int rN = 0;
#include <stdio.h> int repeat;
#include <mpi.h> for (i = 0; i < k; i++)
int main(int argc,char *argv[]) {
{ do
int i,k,p,size,rank; {
int rank_gr; repeat = 0;
char rN = rand() % size;
processor_name[MPI_MAX_PROCESS for (int j = 0; j < i; ++j)
OR_NAME]; {
MPI_Status status; if (rN == ranks[j])
MPI_Group MPI_GROUP_WORLD, newgr; {
int* ranks; repeat = 1;
int namelen; break;
MPI_Init(&argc,&argv); }
MPI_Comm_size(MPI_COMM_WORLD, }
&size); } while(repeat == 1);
MPI_Comm_rank(MPI_COMM_WORLD,
&rank); ranks[i] = rN;
MPI_Get_processor_name(processor_name }
,&namelen); if(rank==0)
if (rank ==0) {
printf("\n=====REZULTATUL PROGRAMULUI printf("Au fost extrase aleator %d
'%s' \n",argv[0]); numere dupa cum urmeaza:\n",k);
MPI_Barrier(MPI_COMM_WORLD); for (i = 0; i < k; i++)
srand(time(0)); printf(" %d ",ranks[i]);
k=size / 2; printf(" \n");
ranks = (int*)malloc(k*sizeof(int)); MPI_Barrier(MPI_COMM_WORLD);

65
} if (rank_gr != MPI_UNDEFINED)
else printf ("Sunt procesul cu rankul %d
MPI_Barrier(MPI_COMM_WORLD); (%d) de pe nodul %s. \n", rank_gr,
MPI_Comm_group(MPI_COMM_WOR rank, processor_name);
LD,&MPI_GROUP_WORLD); MPI_Group_free(&newgr);
MPI_Group_incl(MPI_GROUP_WORLD MPI_Finalize();
,k,ranks,&newgr); return 0;
MPI_Group_rank(newgr,&rank_gr); }

Rezultatele posibile ale executrii programului:


[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o Exemplu _3_5_1.exe Exemplu _3_5_1.cpp
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpirun -n 16 -machinefile ~/nodes
HB_Grup_Proc_Aleatoare.exe
=====REZULTATUL PROGRAMULUI 'Exemplu _3_5_1.exe'
Au fost extrase aleator 8 numere dupa cum urmeaza:
8 2 6 1 14 3 10 13
Sunt procesul cu rankul 2 (6) de pe nodul compute-0-4.local.
Sunt procesul cu rankul 7 (13) de pe nodul compute-0-8.local.
Sunt procesul cu rankul 0 (8) de pe nodul compute-0-6.local.
Sunt procesul cu rankul 3 (1) de pe nodul compute-0-2.local.
Sunt procesul cu rankul 4 (14) de pe nodul compute-0-8.local.
Sunt procesul cu rankul 6 (10) de pe nodul compute-0-6.local.
Sunt procesul cu rankul 1 (2) de pe nodul compute-0-2.local.
Sunt procesul cu rankul 5 (3) de pe nodul compute-0-2.local.
[Hancu_B_S@hpc]$

3.5.3 Funciile MPI de gestionare a comunicatoarelor


n acest paragraf vom analiza funciile de lucru cu comunicatorii.
Acestea sunt mprite n funcii de acces la comunicator i funcii utilizate
pentru a crea un comunicator. Funciile de acces sunt locale i nu necesit
comunicaii, spre deosebire de funciile de creare a comunicatoarelor, care
sunt colective i pot necesita comunicaii interprocesor. Dou funcii de
acces la comunicator MPI_Comm_size i MPI_Comm_rank au fost
analizate deja mai sus.
Funcia MPI_Comm_compare
Este utilizat pentru a compara dou comunicatoare. Prototipul
funciei n limbajul C++ este
int MPI_Comm_compare(MPI_Comm comm1,MPI_Comm
comm2, int *result)
unde
IN comm1 numele primului comunicator;
IN comm2 numele comunicatorului al doilea;
OUT result rezultatul comparaiei.
66
Valorile posibile ale variabilei result:
MPI_IDENT Comunicatoarele
- sunt identice, reprezint acelai
mediu de comunicare.
MPI_CONGRUENT Comunicatoarele sunt congruente, dou medii de
comunicare cu aceeai parametri de grup.
MPI_SIMILAR Comunicatoarele sunt similare, grupele conin
aceleai procese, dar cu o alt distribuire a
rankurilor.
MPI_UNEQUAL Toate celelalte cazuri.
Crearea unor noi medii de comunicare, comunicatoare, se poate face
cu una din urmtoarele funcii: MPI_Comm_dup,
MPI_Comm_create, MPI_Comm_split.
Funcia MPI_Comm_dup
Crearea unui comunicator ca i copie a altuia. Prototipul funciei n
limbajul C++ este
int MPI_Comm_dup(MPI_Comm comm, MPI_Comm
*newcomm))
unde
IN comm numele comunicatorului printe;
OUT newcomm numele comunicatorului nou creat.

Funcia MPI_Comm_create
Funcia este utilizat pentru crearea unui comunicator pentru un grup
de procese. Prototipul funciei n limbajul C++ este
int MPI_Comm_create(MPI_Comm comm, MPI_Group
group, MPI_Comm *newcomm)
unde
IN comm numele comunicatorului printe;
IN group numele grupei;
OUT newcomm numele comunicatorului nou creat.
Pentru procesele care nu fac parte din grupul group se returneaz
valoarea MPI_COMM_NULL. Funcia va returna un cod de eroare dac
group nu este un subgrup al comunicatorului printe.
Vom exemplifica rutinele MPI pentru gestionarea comunicatoarelor
descrise mai sus.

67
Exemplul 3.5.2 S se elaboreze un program MPI n limbajul C++ n
care se creeaz un nou grup de procese care conine cte un singur proces
de pe fiecare nod al clusterului. Pentru acest grup de procese s se creeze
un comunicator. Fiecare proces din comunicatorul creat tiprete
informaia despre sine n forma: rankul din comunicatorul nou (rankul din
comunicatorul printe).
Elaborarea comunicatoarelor de acest tip poate fi utilizat pentru a
exclude transmiterea mesajelor prin rutine MPI ntre procese care aparin
aceluiai nod. Mai jos este prezentat codul programului n limbajul C++ n
care se realizeaz cele menionate n exemplul 3.5.2.
#include <stdio.h> MPI_Allreduce(&k, &Nodes, 1, MPI_INT,
#include <stdlib.h> MPI_SUM, MPI_COMM_WORLD);
#include <mpi.h> newGroup=(int *)malloc(Nodes*sizeof(int));
int main(int argc, char *argv[]) ranks = (int *) malloc(size * sizeof(int));
{ int r;
int i, p, k=0, size, rank, rank_new; // Se construieste vectorul newGroup
int Node_rank; if (local_rank == 0)
int Nodes; //numarul de noduri ranks[rank] = rank;
int local_rank = else
atoi(getenv("OMPI_COMM_WORLD_L ranks[rank] = -1;
OCAL_RANK")); for (int i = 0; i < size; ++i)
char processor_name[MPI_MAX_ MPI_Bcast(&ranks[i], 1, MPI_INT, i,
PROCESSOR_NAME]; MPI_COMM_WORLD);
MPI_Status status; for (int i = 0, j = 0; i < size; ++i)
MPI_Comm com_new, ring1; {
MPI_Group MPI_GROUP_WORLD, newgr; if (ranks[i] != -1)
int *ranks,*newGroup; {
int namelen; newGroup[j] = ranks[i];
MPI_Init(&argc, &argv); ++j;
MPI_Comm_size(MPI_COMM_WORLD, }
&size); }
MPI_Comm_rank(MPI_COMM_WORLD, MPI_Comm_group(MPI_COMM_WORLD,
&rank); &MPI_GROUP_WORLD);
MPI_Get_processor_name(processor_name MPI_Group_incl(MPI_GROUP_WORLD,
, &namelen); Nodes, newGroup, &newgr);
if (rank == 0) { MPI_Comm_create(MPI_COMM_WORLD,
printf("=====REZULTATUL PROGRAMULUI newgr, &com_new);
'%s' \n", argv[0]); MPI_Group_rank(newgr, &rank_new);
printf ("Rankurile proceselor din comuni if (rank_new!= MPI_UNDEFINED)
catorului 'MPI_COMM_WOLD' au fost printf ("Procesul cu rankul %d al com.
repartizate astfel: \n"); } 'com_new' (%d com.
MPI_Barrier(MPI_COMM_WORLD); 'MPI_COMM_WOLD') de pe nodul %s.
// Se determina numarul de noduri (egal cu \n", rank_new,rank,processor_name);
numarul de procese n grupul creat MPI_Finalize();
printf ("Rankul %d de pe nodul %s. \n",rank, return 0;
processor_name); }
if (local_rank == 0) k = 1;

68
Rezultatele posibile ale executrii programului:
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o Exemplu_3_5_2.exe Exemplu_3_5_2.cpp
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpirun -n 28 -host compute-0-1,compute-0-2,compute-0-
4,compute-0-6,compute-0-8,compute-0-9,compute-0-10 Exemplu_3_5_2.exe
=====REZULTATUL PROGRAMULUI 'Exemplu_3_5_2.exe'
Rankurile proceselor din comunicatorului 'MPI_COMM_WOLD' au fost repartizate astfel:
Rankul 18 de pe nodul compute-0-8.local.
Rankul 19 de pe nodul compute-0-9.local.
Rankul 14 de pe nodul compute-0-1.local.
Rankul 6 de pe nodul compute-0-10.local.
Rankul 22 de pe nodul compute-0-2.local.
Rankul 2 de pe nodul compute-0-4.local.
Rankul 10 de pe nodul compute-0-6.local.
Rankul 11 de pe nodul compute-0-8.local.
Rankul 12 de pe nodul compute-0-9.local.
Rankul 0 de pe nodul compute-0-1.local.
Rankul 20 de pe nodul compute-0-10.local.
Rankul 8 de pe nodul compute-0-2.local.
Rankul 9 de pe nodul compute-0-4.local.
Rankul 3 de pe nodul compute-0-6.local.
Rankul 25 de pe nodul compute-0-8.local.
Rankul 26 de pe nodul compute-0-9.local.
Rankul 7 de pe nodul compute-0-1.local.
Rankul 27 de pe nodul compute-0-10.local.
Rankul 15 de pe nodul compute-0-2.local.
Rankul 16 de pe nodul compute-0-4.local.
Rankul 17 de pe nodul compute-0-6.local.
Rankul 4 de pe nodul compute-0-8.local.
Rankul 5 de pe nodul compute-0-9.local.
Rankul 21 de pe nodul compute-0-1.local.
Rankul 13 de pe nodul compute-0-10.local.
Rankul 1 de pe nodul compute-0-2.local.
Rankul 23 de pe nodul compute-0-4.local.
Rankul 24 de pe nodul compute-0-6.local.
Pprocesul cu rankul 3 al com. 'com_new'(3 com. 'MPI_COMM_WOLD') de pe nodul compute-0-6.local.
Procesul cu rankul 5 al com. 'com_new'(5 com. 'MPI_COMM_WOLD') de pe nodul compute-0-9.local.
Procesul cu rankul 6 al com. 'com_new'(6 com. 'MPI_COMM_WOLD') de pe nodul compute-0-10.local.
Procesul cu rankul 1 al com. 'com_new'(1 com. 'MPI_COMM_WOLD') de pe nodul compute-0-2.local.
Procesul cu rankul 2 al com. 'com_new'(2 com. 'MPI_COMM_WOLD') de pe nodul compute-0-4.local.
Procesul cu rankul 4 al com. 'com_new'(4 com. 'MPI_COMM_WOLD') de pe nodul compute-0-8.local.
Procesul cu rankul 0 al com. 'com_new'(0 com. 'MPI_COMM_WOLD') de pe nodul compute-0-1.local.
[Hancu_B_S@hpc]$

3.5.4 Topologii virtuale. Rutine de creare a topologiei carteziene


n termenii MPI, o topologie virtual descrie o aplicaie/ordonare a
proceselor MPI ntr-o form geometric. Cele dou tipuri principale de
topologii suportate de MPI sunt cea cartezian (gril) i cea sub form de
graf. Topologiile MPI sunt virtuale poate s nu existe nicio relaie ntre
structura fizic a unei maini paralele i topologia proceselor. Topologiile
69
virtuale sunt construite pe grupuri i comunicatori MPI i trebuie s fie
programate de cel care dezvolt aplicaia. Topologiile virtuale pot fi utile
pentru aplicaii cu forme de comunicare specifice forme (patterns) care se
potrivesc unei structuri topologice MPI. De exemplu, o topologie cartezian
se poate dovedi convenabil pentru o aplicaie care reclam comunicare cu
4 din vecinii cei mai apropiai pentru date bazate pe grile.

Funcia MPI_Cart_create
Pentru a crea un comunicator (un mediu virtual de comunicare) cu
topologie cartezian este folosit rutina MPI_Cart_create. Cu aceast
funcie se poate crea o topologie cu numr arbitrar de dimensiuni, i pentru
fiecare dimensiune n mod izolat pot fi aplicate condiii limit periodice.
Astfel, n funcie de care condiii la limit se impun, pentru o topologie
unidimensional obinem sau structur liniar, sau un inel (cerc). Pentru
topologie bidimensional, respectiv, fie un dreptunghi sau un cilindru sau
tor. Prototipul funciei n limbajul C++ este
int MPI_Cart_create(MPI_Comm comm_old,int
ndims,int *dims,int *periods,int
reorder,MPI_Comm *comm_cart)
unde
IN comm_old numele comunicatorului printe;
IN ndims dimensiunea (numrul de axe);
IN dims un vector de dimensiunea ndims n
care se indic numrul de procese pe
fiecare ax;
IN periods un vector logic de dimensiunea ndims
n care se indic condiiile la limit
pentru fiecare ax (true- condiiile
sunt periodice, adic axa este nchis,
false- condiiile sunt neperiodice,
adic axa nu este nchis);
IN reorder o variabil logic, care indic dac se va
face renumerotarea proceselor (true)
sau nu (false);
OUT comm_cart numele comunicatorului nou creat.

70
Funcia este colectiv, adic trebuie s fie executat pe toate procesele
din grupul de procese ale comunicatorului comm_old. Parametrul
reorder=false indic faptul c ID-urile proceselor din noul grup vor fi
aceleai ca i n grupul vechi. Dac reorder=true, MPI va ncerca s le
schimbe cu scopul de a optimiza comunicarea.
Urmtoarele funcii descrise n acest paragraf au un caracter auxiliar
sau informaional.

Funcia MPI_Dims_create
Aceast funcie se utilizeaz pentru determinarea unei configuraii
optimale ale reelei de procese. Prototipul funciei n limbajul C++ este
int MPI_Dims_create(int nnodes, int ndims, int
*dims)
unde
IN nnodes numrul total de noduri n reea;
IN ndims dimensiunea (numrul de axe);
IN/OUT dims un vector de dimensiunea ndims n care
se indic numrul recomandat de procese
pe fiecare ax.
La intrare n procedur n vectorul dims trebuie s fie nregistrate
numere ntregi non-negative. n cazul n care elementul dims[i] este un
numr pozitiv, atunci pentru aceast ax (direcie) nu este realizat niciun
calcul (numrul de procese de-a lungul acestei direcii este considerat a fi
specificat). Se determin (calculeaz) numai acele dims[i], care nainte
de aplicarea procedurii sunt setate la 0. Funcia are ca scop crearea unei
distribuii ct mai uniforme a proceselor de-a lungul axei, aranjndu-le n
ordine descresctoare. De exemplu, pentru 12 procese, aceasta va construi o
gril tridimensional 431. Rezultatul acestei rutine poate fi folosit ca un
parametru de intrare pentru funcia MPI_Cart_create.

Funcia MPI_Cart_get
Aceast funcie se utilizeaz pentru a obine o informaie detaliat
despre comunicatorul cu topologie cartezian. Prototipul funciei n
limbajul C++ este
int MPI_Cart_get(MPI_Comm comm, int ndims, int
*dims, int *periods, int *coords)
unde
71
IN comm comunicatorul cu topologie cartezian;
IN ndims dimensiunea (numrul de axe);
OUT dims un vector de dimensiunea ndims n care se
returneaz numrul de procese pe fiecare ax;
OUT periods un vector logic de dimensiunea ndims n
care se returneaz condiiile la limit pentru
fiecare ax (true- condiiile sunt periodice,
false- condiiile sunt neperiodice);
OUT coords coordonatele carteziene ale proceselor care
apeleaz aceast funcie.
Urmtoarele dou funcii realizeaz corespondena dintre rankul
(identificatorul) procesului n comunicatorul pentru care s-a creat topologia
cartezian i coordonatele sale ntr-o gril cartezian.

Funcia MPI_Cart_rank
Aceast funcie se utilizeaz pentru a obine rankul proceselor n baza
coordonatelor sale. Prototipul funciei n limbajul C++ este
int MPI_Cart_rank(MPI_Comm comm, int *coords,
int *rank)
unde
IN comm comunicatorul cu topologie cartezian;
IN coords coordonatele n sistemul cartezian);
OUT rank rankul (identificatorul) procesului.
Funcia MPI_Cart_coords
Aceast funcie se utilizeaz pentru a obine coordonatele carteziene
ale proceselor n baza rankurilor (identificatoarelor) sale. Prototipul funciei
n limbajul C++ este
int MPI_Cart_coords(MPI_Comm comm, int rank, int
ndims, int *coords)
unde
IN comm comunicatorul cu topologie cartezian;
IN rank rankul (identificatorul) procesului;
IN ndims numrul de axe (direcii);
OUT coords coordonatele n sistemul cartezian.

Funcia MPI_Cart_shift

72
n muli algoritmi numerici se utilizeaz operaia de deplasare a
datelor de-a lungul unor axe ale grilei carteziene. n MPI exist funcia
MPI_Cart_shift care realizeaz aceast operaie. Mai precis,
deplasarea datelor este realizat folosind MPI_Sendrecv, iar funcia
MPI_Cart_shift calculeaz pentru fiecare proces parametrii funciei
MPI_Sendrecv funcia (sursa i destinaia). Prototipul funciei n
limbajul C++ este
int MPI_Cart_shift(MPI_Comm comm, int direction,
int disp, int *rank_source, int *rank_dest)
unde
IN comm comunicatorul cu topologie cartezian;
IN direction numrul axei (direciei) de-a lungul
creia se realizeaz deplasarea;
IN disp valoarea pasului de deplasare (poate fi
pozitiv deplasare n direcia acelor
cronometrului, sau negative deplasare
n direcia invers acelor
cronometrului);
OUT rank_source rankul procesului de la care se vor
recepiona datele;
OUT rank_dest rankul procesului care va recepiona
datele.
Vom exemplifica rutinele MPI pentru gestionarea comunicatoarelor cu
topologie cartezian.
Exemplul 3.5.3 S se elaboreze un program MPI n limbajul C++ n
care se creeaz un nou grup de procese care conine cte un singur proces
de pe fiecare nod al clusterului. Pentru acest grup de procese s se creeze
un comunicator cu topologie cartezian de tip cerc (inel) i s se realizeze
transmiterea mesajelor pe cerc.
Mai jos este prezentat codul programului n limbajul C++ n care se
realizeaz cele indicate n exemplul 3.5.3.
#include <stdio.h> int i, p, k=0, size, size_new, rank, rank_new,
#include <stdlib.h> sour, dest;
#include <mpi.h> int Node_rank, rank_gr;
int main(int argc, char *argv[]) int Nodes;
{

73
int local_rank = }
atoi(getenv("OMPI_COMM_WORLD_ }
LOCAL_RANK")); MPI_Comm_group(MPI_COMM_WORLD,
char &MPI_GROUP_WORLD);
processor_name[MPI_MAX_PROCESS MPI_Group_incl(MPI_GROUP_WORLD,
OR_NAME]; Nodes, newGroup, &newgr);
MPI_Status status; MPI_Comm_create(MPI_COMM_WORLD,
MPI_Comm com_new, ring1; newgr, &com_new);
MPI_Group MPI_GROUP_WORLD, newgr; MPI_Group_rank(newgr, &rank_gr);
int dims[1], period[1], reord; if (rank_gr != MPI_UNDEFINED) {
int *ranks,*newGroup; MPI_Comm_size(com_new,
int namelen; &size_new);
int D1 = 123, D2; MPI_Comm_rank(com_new,
MPI_Init(&argc, &argv); &rank_new);
MPI_Comm_size(MPI_COMM_WORLD, dims[0] = size_new;
&size); period[0] = 1;
MPI_Comm_rank(MPI_COMM_WORLD, reord = 1;
&rank); MPI_Cart_create(com_new, 1, dims,
MPI_Get_processor_name(processor_name period, reord, &ring1);
, &namelen); MPI_Cart_shift(ring1, 1, 2, &sour,
if (rank == 0) &dest);
printf("=====REZULTATUL PROGRAMULUI D1 = D1 + rank;
'%s' \n", argv[0]); MPI_Sendrecv(&D1, 1, MPI_INT, dest,
MPI_Barrier(MPI_COMM_WORLD); 12, &D2, 1, MPI_INT, sour, 12, ring1,
if (local_rank == 0) k = 1; &status);
MPI_Allreduce(&k, &Nodes, 1, MPI_INT, if (rank_new == 0) {
MPI_SUM, MPI_COMM_WORLD); printf("===Rezultatul
newGroup=(int *) malloc(Nodes * MPI_Sendrecv:\n");
sizeof(int)); MPI_Barrier(com_new);
ranks = (int *) malloc(size * sizeof(int)); }
int r; else MPI_Barrier(com_new);
if (local_rank == 0) printf ("Proc. %d (%d from %s), recv. from
ranks[rank] = rank; proc. %d the value %d and send to
else proc. %d the value %d\n", rank_new,
ranks[rank] = -1; rank,processor_name, sour,
D2,dest,D1);
for (int i = 0; i < size; ++i) MPI_Barrier(com_new);
MPI_Bcast(&ranks[i], 1, MPI_INT, i, MPI_Group_free(&newgr);
MPI_COMM_WORLD); MPI_Comm_free(&ring1);
for (int i = 0, j = 0; i < size; ++i) MPI_Comm_free(&com_new);
{ }
if (ranks[i] != -1) MPI_Finalize();
{ return 0;
newGroup[j] = ranks[i]; }
++j;
Rezultatele posibile ale executrii programului:
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o Exemplu_3_5_3.exe Exemplu_3_5_3.cpp
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpirun -n 44 -machinefile ~/nodes o Exemplu_3_5_3.exe
=====REZULTATUL PROGRAMULUI o Exemplu_3_5_3.exe
===Rezultatul MPI_Sendrecv:
Proc.0 (0 from compute-0-0.local), recv. from proc. 9 the value 159 and send to proc. 2 the value 123
Proc.1 (4 from compute-0-1.local), recv. from proc. 10 the value 163 and send to proc. 3 the value 127
74
Proc.5 (20 from compute-0-6.local), recv. from proc. 3 the value 135 and send to proc. 7 the value 143
Proc.2 (8 from compute-0-2.local), recv. from proc. 0 the value 123 and send to proc. 4 the value 131
Proc.3 (12 from compute-0-4.local), recv. from proc. 1 the value 127 and send to proc. 5 the value 135
Proc.4 (16 from compute-0-5.local), recv. from proc. 2 the value 131 and send to proc. 6 the value 139
Proc.7 (28 from compute-0-8.local), recv. from proc. 5 the value 143 and send to proc. 9 the value 151
Proc.6 (24 from compute-0-7.local), recv. from proc. 4 the value 139 and send to proc. 8 the value 147
Proc.8 (32 from compute-0-9.local), recv. from proc. 6 the value 147 and send to proc. 10 the value
155
Proc.9 (36 from compute-0-10.local), recv. from proc. 7 the value 151 and send to proc. 0 the value
159
Proc.10 (40 from compute-0-12.local), recv. from proc. 8 the value 155 and send to proc. 1 the value
163
[Hancu_B_S@hpc]$

Exemplul 3.5.4 S se elaboreze un program MPI n limbajul C++ n


care se creeaz un comunicator cu topologie cartezian de tip cub i s se
determine pentru un anumit proces vecinii si pe fiecare ax de
coordonate.
Mai jos este prezentat codul programului n limbajul C++ n care se
realizeaz cele indicate n exemplul 3.5.4.
#include <mpi.h> cout << endl;
#include <stdio.h> }
#include <iostream> MPI_Barrier(MPI_COMM_WORLD);
using namespace std; MPI_Cart_create(MPI_COMM_WORLD,
int main(int argc, char *argv[]) ndims, dims, periods,
{ reorder,&comm);
int rank,test_rank=2; MPI_Cart_coords(comm, rank, ndims,
int size; coords);
int ndims = 3; cout << "Procesul cu rankul " << rank << "
int source, dest; are coordonatele (" << coords[0] << ","
int up_y,down_y,right_x,left_x,up_z, << coords[1] << "," << coords[2]
down_z; <<")"<< endl;
int dims[3]={0,0,0},coords[3]={0,0,0}; MPI_Barrier(MPI_COMM_WORLD);
int periods[3]={0,0,0}, if(rank == test_rank)
reorder = 0; {
MPI_Comm comm; MPI_Cart_shift(comm,0,1,&left_x,
MPI_Init(&argc, &argv); &right_x);
MPI_Comm_size(MPI_COMM_WORLD, MPI_Cart_shift(comm,1,1,&up_y,
&size); &down_y);
MPI_Comm_rank(MPI_COMM_WORLD, MPI_Cart_shift(comm,2,1,&up_z,
&rank); &down_z);
MPI_Dims_create(size, ndims, dims); printf("Sunt procesul cu rankul %d, vecinii
if(rank == 0) mei sunt: \n",rank);
{ printf(" pe directia axei X : stanga %d
printf("\n=====REZULTATUL dreapta %d \n",left_x,right_x);
PROGRAMULUI '%s' \n",argv[0]); printf(" pe directia axei Y : stanga %d
for ( int i = 0; i < 3; i++ ) dreapta %d \n",up_y,down_y);
cout << "Numarul de procese pe axa printf(" pe directia axei Z : stanga %d
"<< i<< " este "<< dims[i] << "; "; dreapta %d \n",up_z,down_z);
75
printf("Valorile negative semnifica lipsa MPI_Finalize();
procesului vecin!\n"); return 0;
} }
Rezultatele posibile ale executrii programului:
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o Exemplu_3_5_4.exe Exemplu_3_5_4.cpp
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpirun -n 16 -machinefile ~/nodes4 Exemplu_3_5_4.exe
=====REZULTATUL PROGRAMULUI ' Exemplu_3_5_4.exe'
Numarul de procese pe axa 0 este 4; Numarul de procese pe axa 1 este 2; Numarul de procese pe axa
2 este 2;
Procesul cu rankul 14 are coordonatele (3,1,0)
Procesul cu rankul 5 are coordonatele (1,0,1)
Procesul cu rankul 0 are coordonatele (0,0,0)
Procesul cu rankul 10 are coordonatele (2,1,0)
Procesul cu rankul 12 are coordonatele (3,0,0)
Procesul cu rankul 4 are coordonatele (1,0,0)
Procesul cu rankul 2 are coordonatele (0,1,0)
Procesul cu rankul 9 are coordonatele (2,0,1)
Procesul cu rankul 13 are coordonatele (3,0,1)
Procesul cu rankul 6 are coordonatele (1,1,0)
Procesul cu rankul 1 are coordonatele (0,0,1)
Procesul cu rankul 11 are coordonatele (2,1,1)
Procesul cu rankul 3 are coordonatele (0,1,1)
Procesul cu rankul 8 are coordonatele (2,0,0)
Procesul cu rankul 15 are coordonatele (3,1,1)
Procesul cu rankul 7 are coordonatele (1,1,1)
Sunt procesul cu rankul 2, vecinii mei sunt:
pe directia axei X : stanga -2 dreapta 6
pe directia axei Y : stanga 0 dreapta -2
pe directia axei Z : stanga -2 dreapta 3
Valorile negative semnifica lipsa procesului vecin!
[Hancu_B_S@hpc]$

3.5.5 Exerciii
1. S se elaboreze i s se execute pe clusterul USM un program MPI n
limbajul C++ n care se creeaz un grup de procese, al cror rank k se
mparte fr rest la 3. Procesele din grupul nou creat tipresc rankul lor
i numele nodului.
2. S se elaboreze i s se execute pe clusterul USM un program MPI n
limbajul C++ n care se creeaz un comunicator cu topologie de tip cerc
pentru un grup de procese extrase aleator din grupul printe. Procesele
din comunicatorul nou creat transmit unul altuia rankul lor.
3. S se elaboreze i s se execute pe clusterul USM un program MPI n
limbajul C++ n care se creeaz dou grupe de procese, nct ntrun grup

76
se realizeaz transmiterea datelor pe cerc i n altul n baza schemei
master-slave.
4. S se elaboreze i s se execute pe clusterul USM un program MPI n
limbajul C++ n care se creeaz un comunicator cu topologie de tip cub.
S se realizeze comunicarea pe cerc a proceselor care aparin unei i
aceleiai faete ale cubului.
5. Fie dat o matrice A aij i 1, m care este divizat n blocuri Akp de
j 1, n
dimensiunea mk m p . S se elaboreze i s se execute pe clusterul
USM un program MPI n limbajul C++ n care se creeaz un
comunicator cu topologie cartezian de dimensiunea 2 i procesul cu
rankul 0, care iniializeaz matricea, trimite procesului cu coordonatele
k, p submatricea Akp . Fiecare proces din topologia nou creat
tiprete submatricea primit.

3.6 Accesul distant la memorie. Funciile RMA

3.6.1 Comunicare unic-direcionat


Funciile RMA (Remote Memory Access) extind mecanismele de
comunicare MPI, permind unui proces s specifice parametrii de
comunicare pentru ambele procese implicate n transmiterea de date. Prin
aceasta, un proces poate citi, scrie sau actualiza date din memoria altui
proces, fr ca al doilea proces s fie explicit implicat n transfer. Operaiile
de trimitere, recepionare i actualizare a datelor sunt reprezentate n MPI
prin funciile MPI_Put, MPI_Get, MPI_accumulate. n afara
acestora, MPI furnizeaz operaii de iniializare, MPI_Win_create, care
permit fiecrui proces al unui grup s specifice o fereastr n memoria sa,
pus la dispoziia celorlalte pentru executarea funciilor RMA, i
MPI_Win_free pentru eliberarea ferestrei, ca i operaii de sincronizare a
proceselor care fac acces la datele altui proces. Comunicrile RMA se pot
mpri n urmtoarele dou categorii:
Cu int activ, n care toate datele sunt mutate din memoria unui
proces n memoria altuia i ambele sunt implicate n mod implicit.
Un proces prevede toate argumentele pentru comunicare, al doilea
particip doar la sincronizare.

77
Cu int pasiv, unde datele sunt mutate din memoria unui proces n
memoria altui proces, i numai unul este implicat n mod implicit n
comunicaie dou procese pot comunica fcnd acces la aceeai
locaie ntr-o fereastr a unui al treilea proces (care nu particip
explicit la comunicare).
Pentru comoditate, vom numi iniiator (origin) procesul care face un
apel la o funcie RMA i destinatar (target) procesul la memoria cruia
se face adresarea. Astfel, pentru operaia put sursa de date este procesul
iniiator (source=origin), locul de destinaie al datelor este procesul
destinatar (destination=target); i pentru operaia get sursa de date
o constituie procesul destinatar (source=target) i locul de destinaie
al datelor este procesul iniiator (destination=origin).

Funcia MPI_Win_create
Pentru a permite accesul memoriei de la distan un proces trebuie s
selecteze o regiune continu de memorie i s o fac accesibil pentru alt
proces. Aceast regiune se numete fereastr. Cellalt proces trebuie s tie
despre aceast fereastr. MPI realizeaz aceasta prin funcia colectiv
MPI_Win_create. Pentru c este o funcie colectiv, toate procesele
trebuie s deschid o fereastr, dar dac un proces nu trebuie s partajeze
memoria sa, el poate defini fereastra de dimensiunea 0 (dimensiunea
ferestrei poate fi diferit pentru fiecare proces implicat n comunicare.
Prototipul funciei n limbajul C++ este
int MPI_Win_create(void *base, MPI_Aint size,
int disp_unit, MPI_Info info,MPI_Comm comm,
MPI_Win *win)
unde
IN base adresa iniial (de start) a ferestrei care
se indic prin numele unei variabile;
IN size dimensiunea ferestrei n octei;
IN disp_unit valoarea n octei a deplasrii;
IN info argumentul pentru informaie
suplimentar;
IN comm numele comunicatorului;
OUT win numele ferestrei.
Funcia MPI_Win_free
78
Aceast funcie elibereaz memoria ocupat de win, returneaz o
valoare egal cu MPI_WIN_NULL. Apelul este colectiv i se realizeaz de
toate procesele din grupul asociat ferestrei win. Prototipul funciei n
limbajul C++ este
int MPI_Win_free(MPI_Win *win)
unde
IN/OUT win numele ferestrei.

3.6.2 Funciile RMA


Funcia MPI_Put
Executarea unei operaii put este similar executrii unei operaii send
de ctre procesul iniiator i, respectiv, receive de ctre procesul destinatar.
Diferena const n faptul c toi parametrii necesari executrii operaiilor
send-receive sunt pui la dispoziie de un singur apel realizat de procesul
iniiator. Prototipul funciei n limbajul C++ este
int MPI_Put(void *origin_addr, int origin_count,
MPI_Datatype origin_datatype, int
target_rank, MPI_Aint target_disp, int
target_count, MPI_Datatype target_datatype,
MPI_Win win)
unde
IN origin_addr adresa iniial (de start) a tamponului
(buffer) pentru procesul iniiator;
IN origin_count numrul de elemente al datelor din
tamponul procesului iniiator;
IN origin_datatype tipul de date din tamponul procesului
iniiator;
IN target_rank rankul procesului destinatar;
IN target_disp deplasarea pentru fereastra
procesului destinatar;
IN target_count numrul de elemente al datelor din
tamponul procesului destinatar;
IN target _datatype tipul de date din tamponul procesului
destinatar;
OUT win numele ferestrei utilizate pentru

79
comunicare de date.
Astfel, n baza funciei MPI_Put procesul iniiator (adic care
execut funcia) transmite origin_count date de tipul origin_datatype,
pornind de la adresa origin_addr procesului determinat de target_rank.
Datele sunt scrise n tamponul procesului destinaie la adresa
target_addr=window_base + target_disp * disp_unit, unde window_base
i disp_unit sunt parametrii ferestrei determinate de procesul destinaie prin
executarea funciei MPI_Win_create. Tamponul procesului destinaie
este determinat de parametrii target_count i target_datatype.
Transmisia de date are loc n acelai mod ca i n cazul n care
procesul iniiator a executat funcia MPI_Send cu parametrii origin_addr,
origin_count, origin_datatype, target rank, tag, comm, iar procesul
destinaie a executat funcia MPI_Receive cu parametrii taget_addr,
target_datatype, source, tag, comm, unde target_addr este adresa
tamponului pentru procesul destinaie, calculat cum s-a explicat mai sus, i
com comunicator pentru grupul de procese care au creat fereastra win.

Funcia MPI_Get
Executarea unei operaii get este similar executrii unei operaii
receive de ctre procesul destinatar, i respectiv, send de ctre procesul
iniiator. Diferena const n faptul c toi parametrii necesari executrii
operaiilor send-receive sunt pui la dispoziie de un singur apel realizat de
procesul destinatar. Prototipul funciei n limbajul C++ este
int MPI_Get(void *origin_addr, int origin_count,
MPI_Datatype origin_datatype, int
target_rank, MPI_Aint target_disp, int
target_count, MPI_Datatype target_datatype,
MPI_Win win).

Funia MPI_Get este similar funciei MPI_Put, cu excepia


faptului c transmiterea de date are loc n direcia opus. Datele sunt
copiate din memoria procesului destinatar n memoria procesului iniiator.

Funcia MPI_Acumulate
Este adesea util n operaia put de a combina mai degrab datele
transferate la procesul-destinatar cu datele pe care le deine, dect de a face
80
nlocuirea (modificarea) datelor n procesul-iniiator. Acest lucru permite,
de exemplu, de a face acumularea unei sume de date, oblignd procesele
implicate s contribuie prin adugarea la variabila de sumare, care se afl n
memoria unui anumit proces. Prototipul funciei n limbajul C++ este
int MPI_Accumulate(void *origin_addr, int
origin_count,MPI_Datatype origin_datatype,
int target_rank,MPI_Aint target_disp, int
target_count,MPI_Datatype target_datatype,
MPI_Op op, MPI_Win win)
unde
IN origin_addr adresa iniial (de start) a tamponului
(buffer) pentru procesul iniiator;
IN origin_count numrul de elemente al datelor din
tamponul procesului iniiator;
IN origin_datatype tipul de date din tamponul procesului
iniiator;
IN target_rank rankul procesului destinatar;
IN target_disp deplasarea pentru fereastra procesului
destinatar;
IN target_count numrul de elemente al datelor din
tamponul procesului destinatar;
IN target _datatype tipul de date din tamponul procesului
destinatar;
IN op operaia de reducere;
OUT win numele ferestrei utilizate pentru
comunicare de date.
Aceast funcie nmagazineaz (acumuleaz) coninutul memoriei
tampon a procesului iniiator (care este determinat de parametrii
origin_addr, origin_datatype i origin_count) n memoria tampon
determinat de parametrii target_count i target_datatype, target_disp ai
ferestrei win a procesului target_rank, folosind operaia de reducere op.
Funcia este similar funciei MPI_Put cu excepia faptului c datele sunt
acumulate n memoria procesului destinatar. Aici pot fi folosite oricare
dintre operaiunile definite pentru funcia MPI_Reduce. Operaiile
definite de utilizator nu pot fi utilizate.

81
Funcia MPI_Win_fencee
Aceast funcie se utilizeaz pentru sincronizarea colectiv a
proceselor care apeleaz funciile RMA (MPI_Put, MPI_Get,
MPI_accumulate). Astfel orice apel al funciilor RMA trebuie bordat
cu funcia MPI_Win_fence. Prototipul funciei n limbajul C++ este
int MPI_Win_fence(int assert, MPI_Win win)
unde
IN assert un numr ntreg;
IN win numele ferestrei.
Argumentul assert este folosit pentru a furniza diverse optimizri a
procesului de sincronizare.
Vom ilustra rutinele MPI pentru accesul distant la memorie prin
urmtorul exemplu.
Exemplul 3.6.1 S se calculeze valoarea aproximativ a lui prin
1 4
integrare numeric cu formula = 0 , folosind formula
1+ 2
dreptunghiurilor. Intervalul nchis [0,1] se mparte ntr-un numr de n
subintervale i se nsumeaz ariile dreptunghiurilor avnd ca baz fiecare
subinterval. Pentru execuia algoritmului n paralel, se atribuie fiecruia
dintre procesele din grup un anumit numr de subintervale. Pentru
realizarea operaiilor colective:
difuzarea valorilor lui n, tuturor proceselor;
nsumarea valorilor calculate de procese.
S se utilizeze funciile RMA.
Mai jos este prezentat codul programului n limbajul C++ care
determin valoarea aproximativ a lui folosind funciile RMA.
#include <stdio.h> char
#include <mpi.h> processor_name[MPI_MAX_PROCESS
#include <math.h> OR_NAME];
double f(double a) MPI_Win nwin, piwin;
{ MPI_Init(&argc,&argv);
return (4.0 / (1.0 + a*a)); MPI_Comm_size(MPI_COMM_WORLD,
} &numprocs);
int main(int argc, char *argv[]) MPI_Comm_rank(MPI_COMM_WORLD,
{ &myid);
double PI25DT = MPI_Get_processor_name(processor_name
3.141592653589793238462643; ,&namelen);
int n, numprocs, myid, i,done = 0; //===Crearea locatiilor de memorie pentru
double mypi, pi, h, sum, x; realizarea RMA
int namelen; if (myid==0)
82
{ if (myid != 0)
MPI_Win_create(&n,sizeof(int),1,MPI_INFO MPI_Get(&n, 1, MPI_INT, 0, 0, 1,
_NULL, MPI_COMM_WORLD, &nwin); MPI_INT, nwin);
MPI_Win_create(&pi,sizeof(double),1, MPI_Win_fence(0,nwin);
MPI_INFO_NULL, if ( n == 0 ) done = 1;
MPI_COMM_WORLD, &piwin); else
} {
else h = 1.0 / (double) n;
{ sum = 0.0;
// procesele nu vor utiliza datele din for (i = myid + 1; i <= n; i += numprocs)
memoria ferestrelor sale nwin i piwin {
MPI_Win_create(MPI_BOTTOM, 0, 1, x = h * ((double)i - 0.5); sum += f(x);
MPI_INFO_NULL, }
MPI_COMM_WORLD, &nwin); mypi = h * sum;
MPI_Win_create(MPI_BOTTOM, 0, 1, MPI_Win_fence(0, piwin);
MPI_INFO_NULL, MPI_Accumulate(&mypi, 1,MPI_DOUBLE, 0,
MPI_COMM_WORLD, &piwin); 0, 1,MPI_DOUBLE,MPI_SUM, piwin);
} MPI_Win_fence(0, piwin);
while (!done) if (myid == 0) {
{ printf("For number of intervals %d pi is
if (myid == 0) approximately %.16f, Error is %.16f\n
{ ", n, pi, fabs(pi-PI25DT));
printf("Enter the number of intervals: fflush(stdout); }
(0 quits):\n"); }
fflush(stdout); }
scanf("%d",&n); MPI_Win_free(&nwin);
pi=0.0; MPI_Win_free(&piwin);
} MPI_Finalize();
//Procesele cu rank diferit de 0 "citesc" return 0;
variabila n a procesului cu rank 0 }
MPI_Win_fence(0,nwin);

Rezultatele posibile ale executrii programului:


[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o Exemplu_3_6_1.exe Exemplu_3_6_1.cpp
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpirun -n 16 -machinefile ~/nodes4 Exemplu_3_6_1.exe
Enter the number of intervals: (0 quits):
100000
For number of intervals 100000 pi is approximately 3.1415926535981260, Error is
0.0000000000083329
Enter the number of intervals: (0 quits):
100000000
For number of intervals 100000000 pi is approximately 3.1415926535897749, Error is
0.0000000000000182
Enter the number of intervals: (0 quits):
0
[Hancu_B_S@hpc]$

83
3.6.3 Exerciii
1. S se realizeze o analiz comparativ a timpului de execuie pentru
programele MPI descrise n exemplul 3.6.1 i exemplul 3.4.4.
2. De ce orice apel al funciilor RMA trebuie bordat cu funcia
MPI_Win_fence?
3. Ce funcii RMA se pot utiliza pentru nlocuirea funciei
MPI_Sendrecv?
4. S se elaboreze i s se execute pe clusterul USM un program MPI n
limbajul C++ n care se realizeaz condiiile enunate n exemplul 3.3.1,
utiliznd comunicri unic-direcionate, adic funciile RMA.
5. S se elaboreze i s se execute pe clusterul USM un program MPI n
limbajul C++ n care se realizeaz condiiile enunate n exemplul 3.4.,5
utiliznd comunicri unic-direcionate, adic funciile RMA.

3.7 Generarea dinamic a proceselor MPI

3.7.1 Modaliti de generare dinamic a proceselor


n toate exemplele descrise mai sus s-a folosit un singur mod de
generare a proceselor MPI. i anume prin utilizarea comenzii mpirun.
Acest mod de generare a proceselor se numete static.
MPI presupune existena unui mediu de execuie a proceselor, cu care
interaciunea este pstrat la un nivel sczut pentru a evita compromiterea
portabilitii. Interaciunea se limiteaz la urmtoarele aspecte:
Un proces poate porni dinamic alte procese prin funcia
MPI_Comm_spawn i MPI_Comm_spawn_multiple.
Poate comunica printr-un argument info informaii despre unde i
cum sa porneasc procesul.
Un atribut MPI_UNIVERSE_SIZE al MPI_COMM_WORLD
precizeaz cte procese pot rula n total, deci cte pot fi pornite
dinamic, n plus fa de cele n execuie.
Funcia MPI_Comm_spawn
Aceast funcie se utilizeaz pentru generarea unui numr de procese
MPI, fiecare dintre acestea va executa acelai cod de program. Prototipul
funciei n limbajul C++ este
84
int MPI_Comm_spawn(char *command, char *argv[],
int maxprocs, MPI_Info info, int root,
MPI_Comm comm, MPI_Comm *intercomm,int
array_of_errcodes[])
unde
IN command specific numele codului de program
care va fi executat de procesele MPI
generate;
IN argv[] conine argumentele transmise
programului n forma unui tablou de
iruri de caractere;
IN maxprocs numrul de procese generate care vor
executa programul MPI specificat de
command;
IN info conine informaii adiionale pentru
mediul de execuie n forma unor perechi
de iruri de caractere (cheie, valoare);
IN root rankul procesului pentru care sunt
descrise argumentele anterioare (info
etc.);
IN comm intracomunicatorul pentru grupul de
procese care conine procesul generator
(de procese MPI);
OUT intercomm un intercomunicator pentru comunicare
ntre prini i fii;
OUT array_of_ conine codurile de erori.
errcodes[]
Astfel funcia MPI_Comm_spawn ntoarce un intercomunicator
pentru comunicare ntre procese prini i procese fii. Acesta conine
procesele printe n grupul local i procesele fii n grupul distant.

Funcia MPI_Comm_ get_parent


Intercomunicatorul poate fi obinut de procesele fii apelnd aceast
funcie. Prototipul funciei n limbajul C++ este
int MPI_Comm_get_parent(MPI_Comm *parent)
unde
OUT parent specific numele intercomunicatorului
85
printe ;
n cazul n care un proces a fost generat utiliznd funcia
MPI_Comm_spawn sau MPI_Comm_spawn_multiple, atunci funcia
MPI_Comm_get_parent returneaz intercomunicatorul printe pentru
procesul curent. n cazul n care procesul nu a fost generat,
MPI_Comm_get_parent returneaz valoarea MPI_COMM_NULL.
Vom ilustra utilizarea rutinelor MPI descrise mai sus prin urmtorul
exemplu.
Exemplul 3.7.1 S se elaboreze un program MPI n limbajul C++
pentru generarea dinamic a proceselor care, la rndul lor, vor executa
acelai cod de program pe nodurile compute-0-0,compute-0-1 i
compute-0-3.
Mai jos este prezentat codul programului n limbajul C++ n care se
realizeaz cele indicate n exemplul 3.7.1. Codul programului
Exemplu_3_7_1.cpp n care se genereaz procesele.
/* manager */ MPI_Attr_get(MPI_COMM_WORLD,
#include<string.h> MPI_UNIVERSE_SIZE,&universe_sizep,
#include<stdlib.h> &flag);
#include<stdio.h> if (!flag) {
#include <mpi.h> printf("This MPI does not support
int main(int argc, char *argv[]) UNIVERSE_SIZE. How many n
{ processes total?\n");
int world_size, universe_size, scanf("%d", &universe_size);
*universe_sizep, flag, err[4], namelen, } else universe_size =
rank; *universe_sizep;
MPI_Comm everyone; if (universe_size == 1) printf("No room to
char start workers");
worker_program[100]="./HB_MPI_Spa MPI_Info_create(&hostinfo);
wn_Worker.exe"; MPI_Info_set(hostinfo, host, "compute-0-1,
char processor_name[MPI_MAX_ compute-0-0,compute-0-3" );
PROCESSOR_NAME]; universe_size=9;
MPI_Info hostinfo; MPI_Comm_spawn(worker_program,
char *host =(char*)"host"; MPI_ARGV_NULL, universe_size-1,
MPI_Init(&argc, &argv); hostinfo,0,MPI_COMM_WORLD,
MPI_Get_processor_name(processor_name &everyone,err);
,&namelen); printf("===I am Manager ('%s'), run on the
MPI_Comm_size(MPI_COMM_WORLD, node '%s' with rank %d and generate
&world_size); %d proceses that run the program '%s'
MPI_Comm_rank(MPI_COMM_WORLD,&ra ===\n",argv[0],processor_name,rank,u
nk); niverse_size-1,worker_program);
if (world_size != 1) printf("Top heavy with MPI_Finalize();
management"); return 0;
}

86
Codul programului HB_MPI_Spawn_Worker.cpp care va fi
executat de procesele generate:
#include <mpi.h> world_size %d \n",argv[0], rank,
#include <stdio.h> processor_name, size);
int main(int argc, char *argv[]) MPI_Barrier(MPI_COMM_WORLD);
{ if(rank==incep)
int size,size1,rank,namelen,t,incep=3; {
char MPI_Send(&rank,1,MPI_INT, (rank + 1) %
processor_name[MPI_MAX_PROCESS size1, 10, MPI_COMM_WORLD);
OR_NAME]; MPI_Recv(&t,1,MPI_INT, (rank+size1-1) %
MPI_Comm parent; size1,10,MPI_COMM_WORLD,&status
MPI_Status status; );
MPI_Init(&argc, &argv); }
MPI_Comm_get_parent(&parent); else
MPI_Get_processor_name(processor_name {
,&namelen); MPI_Recv(&t,1,MPI_INT, (rank+size1-
if (parent == MPI_COMM_NULL) 1)%size1, 10, MPI_COMM_WORLD,
printf("=== Intercomunicatorul parinte nu a &status);
fost creat!\n"); MPI_Send(&rank,1,MPI_INT,(rank+1)%size1,
MPI_Comm_remote_size(parent, &size); 10,MPI_COMM_WORLD);
MPI_Comm_size(MPI_COMM_WORLD, }
&size1); printf("proc num %d@%s rcvd=%d from
MPI_Comm_rank(MPI_COMM_WORLD, %d\n",rank, processor_name, t, t);
&rank); MPI_Finalize();
if (size != 1) printf("Something's wrong with return 0;
the parent"); }
printf("Module '%s'. Start on the processor
rank %d of the node name '%s'of

Rezultatele posibile ale executrii programului:


[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o Exemplu _3_7_1.exe Exemplu _3_7_1.cpp
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o HB_MPI_Spawn_Worker.exe
HB_MPI_Spawn_Worker.cpp
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpirun -n 1 -machinefile ~/nodes Exemplu _3_7_1.exe
===I am Manager (' Exemplu _3_7_1.exe'), run on the node 'compute-0-0.local' with rank 0 and ===I
am Manager ('Exemplu_3_7_1.exe'), run on the node 'compute-0-0.local' with rank 0 and generate 8
proceses that run the program './HB_MPI_Spawn_Worker.exe' ===
Module './HB_MPI_Spawn_Worker.exe'. Start on the processor rank 2 of the node name 'compute-0-
0.local'of world_size 1
Module './HB_MPI_Spawn_Worker.exe'. Start on the processor rank 5 of the node name 'compute-0-
0.local'of world_size 1
Module './HB_MPI_Spawn_Worker.exe'. Start on the processor rank 0 of the node name 'compute-0-
3.local'of world_size 1
Module './HB_MPI_Spawn_Worker.exe'. Start on the processor rank 3 of the node name 'compute-0-
3.local'of world_size 1
Module './HB_MPI_Spawn_Worker.exe'. Start on the processor rank 6 of the node name 'compute-0-
3.local'of world_size 1
Module './HB_MPI_Spawn_Worker.exe'. Start on the processor rank 1 of the node name 'compute-0-
1.local'of world_size 1
Module './HB_MPI_Spawn_Worker.exe'. Start on the processor rank 4 of the node name 'compute-0-
1.local'of world_size 1
Module './HB_MPI_Spawn_Worker.exe'. Start on the processor rank 7 of the node name 'compute-0-
1.local'of world_size 1
proc num 4@compute-0-1.local rcvd=3 from 3
proc num 5@compute-0-0.local rcvd=4 from 4
proc num 6@compute-0-3.local rcvd=5 from 5
proc num 7@compute-0-1.local rcvd=6 from 6
proc num 0@compute-0-3.local rcvd=7 from 7
proc num 1@compute-0-1.local rcvd=0 from 0
proc num 2@compute-0-0.local rcvd=1 from 1
proc num 3@compute-0-3.local rcvd=2 from 2

Funcia MPI_Comm_spawn_multiple
Aceast funcie se utilizeaz pentru generarea unui numr de procese
MPI, fiecare dintre acestea pot executa coduri diferite de program.
Prototipul funciei n limbajul C++ este
int MPI_Comm_spawn_multiple(int count, char
*array_of_commands[], char
**array_of_argv[], int array_of_maxprocs[],
MPI_Info array_of_info[], int root, MPI_Comm
comm, MPI_Comm *intercomm, int
array_of_errcodes[])
unde
IN count numrul de programe care urmeaz a
fi executate (este relevant numai
pentru procesul root);
IN vector de lungimea count n care
array_of_commands[] elementul i specific numele
codului de program care vor fi
executate de procesele MPI generate
(este relevant numai pentru procesul
root);
IN array_of_argv[] vector de lungimea count n care
se specific argumentele transmise
programelor, n forma unui tablou de
iruri de caractere (este relevant
numai pentru procesul root);
IN array_of_ vector de lungimea count n care
maxprocs[] elementul i specific numrul
maximal de procese care vor executa
88
programul indicat de vectorul
array_of_commands[] (este
relevant numai pentru procesul
root);
IN array_of_info[] vector de lungimea count n care
elementul i conine informaii
adiionale pentru mediul de execuie
n forma unor perechi de iruri de
caractere (cheie, valoare) (este
relevant numai pentru procesul
root) ;
IN root rankul procesului pentru care sunt
descrise argumentele anterioare;
IN comm intracomunicatorul pentru grupul de
procese care conine procesul
generator (de procese MPI) ;
OUT intercomm un intercomunicator pentru
comunicare ntre prini i fii.
OUT array_of_ conine codurile de erori
errcodes[]

Vom ilustra utilizarea rutinei MPI_Comm_spawn_multiple prin


urmtorul exemplu.
Exemplul 3.7.2 S se elaboreze un program MPI n limbajul C++
pentru generarea dinamic a proceselor, care la rndul lor, vor executa
coduri diferite de program n urmtorul mod
Programul HB_MPI_Spawn_Worker1.exe se va executa pe 8
procesoare ale nodurilor compute-0-0 i compute-0-1.
Programul HB_MPI_Spawn_Worker2.exe se va executa pe 4
procesoare ale nodului compute-0-3.
Programul HB_MPI_Spawn_Worker3.exe se va executa pe 3
procesoare ale nodului compute-0-11.
Mai jos este prezentat codul programului n limbajul C++ n care se
realizeaz cele indicate n exemplul 3.7.2.
Codul programului exemplul_3_7_2.cpp n care se genereaz procesele:
#include <mpi.h> #include <stdio.h>
89
#include<stdlib.h> MPI_Comm_rank(MPI_COMM_WORLD,&ra
int main(int argc, char *argv[]) nk);
{ for(int i = 0; i < count; i++)
int world_size, *universe_sizep, flag, MPI_Info_create(&hostinfo[i]);
err[4],namelen,rank; MPI_Info_set(hostinfo[0], host,"compute-0-
char 0, compute-0-1" );
processor_name[MPI_MAX_PROCESS MPI_Info_set(hostinfo[1], host,"compute-0-
OR_NAME]; 3" );
MPI_Comm everyone; /* MPI_Info_set(hostinfo[2], host,"compute-0-
intercommunicator */ 11" );
const int count = 3; if (world_size != 1) printf("Top heavy with
int universe_size[count] = {8, 4, 3}; management");
char *worker_program[count] = printf("===I am Manager ('%s'), run on
{"./HB_MPI_Spawn_Worker1.exe", the node '%s' with rank %d and
"./HB_MPI_Spawn_Worker2.exe", generate the following proceses:
"./HB_MPI_Spawn_Worker3.exe"}; \n",argv[0],processor_name,rank);
char **args[count]; for(int i = 0; i < count; i++)
char *argv0[] = {NULL}; {
char *argv1[] = {NULL}; printf("%d proceses run the module '%s'\n",
char *argv2[] = {NULL}; universe_size[i],worker_program[i]);
args[0] = argv0; }
args[1] = argv1; printf("===\n");
args[2] = argv2; MPI_Comm_spawn_multiple(count,
MPI_Info hostinfo[count]; worker_program, args, universe_size,
char *host =(char*)"host"; hostinfo, 0,MPI_COMM_SELF,
MPI_Init(&argc, &argv); &everyone,err);
MPI_Get_processor_name(processor_name MPI_Finalize();
,&namelen); return 0;
MPI_Comm_size(MPI_COMM_WORLD, }
&world_size);
Codul programului HB_MPI_Spawn_Worker1.cpp care va fi executat de
procesele generate. Programele HB_MPI_Spawn_Worker2.cpp,
HB_MPI_Spawn_Worker3.cpp sunt similare.
#include <mpi.h> MPI_Comm_remote_size(parent, &size);
#include <stdio.h> MPI_Comm_size(MPI_COMM_WORLD,
int main(int argc, char *argv[]) &size1);
{ MPI_Comm_rank(MPI_COMM_WORLD,
int size,size1,rank,namelen,t,incep=3; &rank);
char if (size != 1) printf("Something's wrong with
processor_name[MPI_MAX_PROCESS the parent");
OR_NAME]; printf("Module '%s'. Start on the processor
MPI_Comm parent; rank %d of the node name '%s'of
MPI_Status status; world_size %d \n",argv[0], rank,
MPI_Init(&argc, &argv); processor_name, size);
MPI_Comm_get_parent(&parent); MPI_Barrier(MPI_COMM_WORLD);
MPI_Get_processor_name(processor_name if(rank==incep)
,&namelen); {
if (parent == MPI_COMM_NULL) MPI_Send(&rank,1,MPI_INT, (rank + 1) %
printf("=== Intercomunicatorul parinte nu a size1, 10, MPI_COMM_WORLD);
fost creat!\n");

90
MPI_Recv(&t,1,MPI_INT, (rank+size1-1) % MPI_Send(&rank,1,MPI_INT,(rank+1)%size1,
size1,10,MPI_COMM_WORLD,&status 10,MPI_COMM_WORLD);
); }
} printf("proc num %d@%s rcvd=%d from
else %d\n",rank, processor_name, t, t);
{ MPI_Finalize();
MPI_Recv(&t,1,MPI_INT, (rank+size1- return 0;
1)%size1, 10, MPI_COMM_WORLD, }
&status);

Rezultatele posibile ale executrii programului:


[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o Exemplu _3_7_2.exe Exemplu _3_7_2.cpp
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o HB_MPI_Spawn_Worker1.exe
HB_MPI_Spawn_Worker1.cpp
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o HB_MPI_Spawn_Worker2.exe
HB_MPI_Spawn_Worker2.cpp
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o HB_MPI_Spawn_Worker3.exe
HB_MPI_Spawn_Worker3.cpp
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpirun -n 1 -machinefile ~/nodes Exemplu _3_7_2.exe
[Hancu_B_S@hpc Notate_Exemple]$ /opt/openmpi/bin/mpirun -n 1 -machinefile ~/nodes6
Exemplu_3_7_2.exe
===I am Manager ('Exemplu_3_7_2.exe'), run on the node 'compute-0-0.local' with rank 0 and
generate the following proceses:
8 proceses run the module './HB_MPI_Spawn_Worker1.exe'
4 proceses run the module './HB_MPI_Spawn_Worker2.exe'
3 proceses run the module './HB_MPI_Spawn_Worker3.exe'
===
Module './HB_MPI_Spawn_Worker2.exe'. Start on the processor rank 8 of the node name 'compute-
0-3.local'of world_size 1
Module './HB_MPI_Spawn_Worker1.exe'. Start on the processor rank 0 of the node name 'compute-
0-1.local'of world_size 1
Module './HB_MPI_Spawn_Worker1.exe'. Start on the processor rank 2 of the node name 'compute-
0-1.local'of world_size 1
Module './HB_MPI_Spawn_Worker1.exe'. Start on the processor rank 4 of the node name 'compute-
0-1.local'of world_size 1
Module './HB_MPI_Spawn_Worker1.exe'. Start on the processor rank 6 of the node name 'compute-
0-1.local'of world_size 1
Module './HB_MPI_Spawn_Worker1.exe'. Start on the processor rank 1 of the node name 'compute-
0-0.local'of world_size 1
Module './HB_MPI_Spawn_Worker1.exe'. Start on the processor rank 3 of the node name 'compute-
0-0.local'of world_size 1
Module './HB_MPI_Spawn_Worker1.exe'. Start on the processor rank 5 of the node name 'compute-
0-0.local'of world_size 1
Module './HB_MPI_Spawn_Worker3.exe'. Start on the processor rank 14 of the node name 'compute-
0-11.local'of world_size 1
Module './HB_MPI_Spawn_Worker3.exe'. Start on the processor rank 12 of the node name 'compute-
0-11.local'of world_size 1
Module './HB_MPI_Spawn_Worker3.exe'. Start on the processor rank 13 of the node name 'compute-
0-11.local'of world_size 1
Module './HB_MPI_Spawn_Worker2.exe'. Start on the processor rank 9 of the node name 'compute-
0-3.local'of world_size 1

91
Module './HB_MPI_Spawn_Worker2.exe'. Start on the processor rank 10 of the node name 'compute-
0-3.local'of world_size 1
Module './HB_MPI_Spawn_Worker2.exe'. Start on the processor rank 11 of the node name 'compute-
0-3.local'of world_size 1
Module './HB_MPI_Spawn_Worker1.exe'. Start on the processor rank 7 of the node name 'compute-
0-0.local'of world_size 1
==Module './HB_MPI_Spawn_Worker1.exe'. proc num 4 @compute-0-1.local rcvd=3 from 3
==Module './HB_MPI_Spawn_Worker1.exe'. proc num 6 @compute-0-1.local rcvd=5 from 5
==Module './HB_MPI_Spawn_Worker1.exe'. proc num 5 @compute-0-0.local rcvd=4 from 4
==Module './HB_MPI_Spawn_Worker1.exe'. proc num 7 @compute-0-0.local rcvd=6 from 6
==Module './HB_MPI_Spawn_Worker3.exe'. proc num 14 @compute-0-11.local rcvd=13 from 13
==Module './HB_MPI_Spawn_Worker3.exe'. proc num 12 @compute-0-11.local rcvd=11 from 11
==Module './HB_MPI_Spawn_Worker2.exe'. proc num 8 @compute-0-3.local rcvd=7 from 7
==Module './HB_MPI_Spawn_Worker1.exe'. proc num 0 @compute-0-1.local rcvd=14 from 14
==Module './HB_MPI_Spawn_Worker2.exe'. proc num 9 @compute-0-3.local rcvd=8 from 8
==Module './HB_MPI_Spawn_Worker3.exe'. proc num 13 @compute-0-11.local rcvd=12 from 12
==Module './HB_MPI_Spawn_Worker2.exe'. proc num 10 @compute-0-3.local rcvd=9 from 9
==Module './HB_MPI_Spawn_Worker1.exe'. proc num 1 @compute-0-0.local rcvd=0 from 0
==Module './HB_MPI_Spawn_Worker2.exe'. proc num 11 @compute-0-3.local rcvd=10 from 10
==Module './HB_MPI_Spawn_Worker1.exe'. proc num 3 @compute-0-0.local rcvd=2 from 2
==Module './HB_MPI_Spawn_Worker1.exe'. proc num 2 @compute-0-1.local rcvd=1 from 1

3.7.2 Comunicarea ntre procesele generate dinamic


MPI permite stabilirea unor canale de comunicare ntre procese,
chiar dac acestea nu mpart un comunicator comun. Aceasta este util n
urmtoarele situaii:
Dou pri ale unei aplicaii, pornite independent, trebuie s
comunice ntre ele.
Un instrument de vizualizare vrea s se ataeze la un proces n
execuie.
Un server vrea s accepte conexiuni de la mai muli clieni; serverul
i clienii pot fi programe paralele.
Mai jos vom prezenta modalitile de comunicare ntre procesul
printe i procesele fii generate.
Vom exemplifica comunicarea ntre procesul printe i procesele fiu
prin intermediul intercomunicatorului (adic ei fac parte din grupuri diferite
de procese) n cele ce urmeaz.
Exemplul 3.7.3 S se elaboreze un program MPI n limbajul C++
pentru generarea dinamic a proceselor, astfel nct fiecare proces fiu va
trimite rankul su procesului printe utiliznd un mediu de comunicare de
tip intercomunicator. Procesele generate se vor executa pe nodurile
compute-0-1, compute-0-0 i compute-0-3.

92
Mai jos este prezentat codul programului n limbajul C++ n care se
realizeaz cele indicate n exemplul 3.7.3.
Codul programului exemplu_3_7_3.cpp n care se genereaz
procesele.
#include <stdio.h> if (size != 1) printf("Error: Only one manager
#include <stdlib.h> process should be running, but %d
#include <string.h> were started.\n", size);
#include <mpi.h> universe_size = 12;
int main(int argc, char** argv) strcpy(worker_program,"./HB_Woker_Com
{ unication.exe");
int rank, size, namelen, version, subversion, MPI_Info_create(&hostinfo);
universe_size; MPI_Info_set(hostinfo, "host","compute-0-
MPI_Comm family_comm; 1, compute-0-0,compute-0-3" );
char printf("Spawning %d worker processes
processor_name[MPI_MAX_PROCESS running %s\n", universe_size-1,
OR_NAME], worker_program);
worker_program[100]; MPI_Comm_spawn(worker_program,
int rank_from_child,ich; MPI_ARGV_NULL,universe_size-1,
MPI_Info hostinfo; hostinfo,0,MPI_COMM_SELF,&family_
MPI_Status status; comm, MPI_ERRCODES_IGNORE);
MPI_Init(&argc,&argv); for(ich=0;ich<(universe_size-1);ich++)
MPI_Comm_rank(MPI_COMM_WORLD, {
&rank); MPI_Recv(&rank_from_child,1,MPI_INT,ich,
MPI_Comm_size(MPI_COMM_WORLD, 0,family_comm,MPI_STATUS_IGNORE);
&size); printf("Received rank %d from child %d
MPI_Get_processor_name(processor_name \n",rank_from_child,ich);
,&namelen); }
MPI_Get_version(&version,&subversion); MPI_Bcast(&rank,1,MPI_INT,MPI_ROOT,
printf("I'm manager %d of %d on %s running family_comm);
MPI %d.%d\n", rank, size, processor_ MPI_Comm_disconnect(&family_comm);
name, version, subversion); MPI_Finalize();
return 0;
}

Codul programului HB_Woker_Comunication.cpp care va fi executat


de procesele generate.
#include <mpi.h> char
#include <stdio.h> processor_name[MPI_MAX_PROCESS
#include <stdio.h> OR_NAME];
#include <stdlib.h> MPI_Init(&argc, &argv);
#include <mpi.h> MPI_Comm_rank(MPI_COMM_WORLD,
int main(int argc, char** argv) &rank);
{ MPI_Comm_size(MPI_COMM_WORLD,
int rank, size, namelen, version, subversion, &size);
psize; MPI_Get_processor_name(processor_name
int parent_rank; ,&namelen);
MPI_Comm parent; MPI_Get_version(&version,&subversion);

93
printf("I'm worker %d of %d on %s running exit(2);
MPI %d.%d\n", rank, size, }
processor_name, version, subversion); /* comunicae cu procesul parinte */
MPI_Comm_get_parent(&parent); Int sendrank=rank;
if (parent == MPI_COMM_NULL) { printf("Worker %d:Success!\n", rank);
printf("Error: no parent process MPI_Send(&rank,1,MPI_INT,0,0,parent);
found!\n"); MPI_Bcast(&parent_rank,1,MPI_INT,0,
exit(1); parent);
} printf("For Woker %d value of rank received
MPI_Comm_remote_size(parent,&psize); from parent is %d \n", rank,
if parent_rank);
(psize!=1) MPI_Comm_disconnect(&parent);
{ MPI_Finalize();
printf("Error: number of parents (%d) should return 0;
be 1.\n", psize); }

Rezultatele posibile ale executrii programului:

[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o Exemplu _3_7_3.exe Exemplu _3_7_3.cpp


[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o HB_Spawn_Comunication.exe
HB_Spawn_Comunication.cpp
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpirun -n 1 -machinefile ~/nodes Exemplu _3_7_3.exe
I'm manager 0 of 1 on compute-0-0.local running MPI 2.1
Spawning 11 worker processes running ./HB_Woker_Comunication.exe
I'm worker 5 of 11 on compute-0-0.local running MPI 2.1
Worker 5:Success!
I'm worker 0 of 11 on compute-0-3.local running MPI 2.1
Worker 0:Success!
I'm worker 8 of 11 on compute-0-0.local running MPI 2.1
Worker 8:Success!
I'm worker 1 of 11 on compute-0-1.local running MPI 2.1
Worker 1:Success!
I'm worker 3 of 11 on compute-0-3.local running MPI 2.1
Worker 3:Success!
I'm worker 6 of 11 on compute-0-3.local running MPI 2.1
Worker 6:Success!
Received rank 0 from child 0
Received rank 1 from child 1
Received rank 2 from child 2
I'm worker 4 of 11 on compute-0-1.local running MPI 2.1
Worker 4:Success!
I'm worker 7 of 11 on compute-0-1.local running MPI 2.1
Worker 7:Success!
I'm worker 9 of 11 on compute-0-3.local running MPI 2.1
Worker 9:Success!
I'm worker 2 of 11 on compute-0-0.local running MPI 2.1
Worker 2:Success!
I'm worker 10 of 11 on compute-0-1.local running MPI 2.1
Worker 10:Success!
Received rank 3 from child 3
Received rank 4 from child 4

94
Received rank 5 from child 5
Received rank 6 from child 6
Received rank 7 from child 7
Received rank 8 from child 8
Received rank 9 from child 9
Received rank 10 from child 10
For Woker 5 value of rank received from parent is 0
For Woker 9 value of rank received from parent is 0
For Woker 8 value of rank received from parent is 0
For Woker 2 value of rank received from parent is 0
For Woker 0 value of rank received from parent is 0
For Woker 3 value of rank received from parent is 0
For Woker 6 value of rank received from parent is 0
For Woker 10 value of rank received from parent is 0
For Woker 1 value of rank received from parent is 0
For Woker 4 value of rank received from parent is 0
For Woker 7 value of rank received from parent is 0
[Hancu_B_S@hpc]$

Pentru realizarea comunicrii ntre procesul printe i procesele fii


prin intermediul uni mediu de comunicare de tip intracomunicator trebuie
utilizat funcia MPI_Intercomm_merge. Prototipul funciei n limbajul
C++ este
int MPI_Intercomm_merge(MPI_Comm intercomm, int
high,MPI_Comm *newintracomm)
unde
IN intercomm numele intercomunicatorului;
IN high o variabil logic care indic modul
de reuniune a grupelor de procese;
OUT newintracomm numele intracomunicatorului.
Vom ilustra comunicarea ntre procesul printe i procesele fii prin
intermediul intracomunicatorului (adic ei fac parte din acelai grup de
procese) prin urmtorul exemplu.
Exemplul 3.7.4 S se elaboreze un program MPI n limbajul C++
pentru generarea dinamic a proceselor, astfel nct procesul fiu va
trimite rankul su procesului printe utiliznd un mediu de comunicare
de tip intracomunicator. Procesele generate se vor executa pe nodurile
compute-0-1, compute-0-0 i compute-0-3.
Mai jos este prezentat codul programului n limbajul C++ n care se
realizeaz cele indicate n exemplul 3.7.4.
Codul programului exemplu_3_7_4.cpp n care se genereaz procesele.
#include <stdio.h> #include <stdlib.h>

95
#include <string.h> universe_size = 12;
#include <mpi.h> MPI_Info_create(&hostinfo);
int main(int argc, char** argv) MPI_Info_set(hostinfo, "host", "compute-0-
{ 1,compute-0-0,compute-0-3" );
int rank, size, namelen, version, subversion, strcpy(worker_program,"./HB_Woker_Com
universe_size; unication_V1.exe");
int globalrank,sumrank; printf("Spawning %d worker processes
MPI_Comm family_comm,allcomm; running %s\n", universe_size-1,
char worker_program);
processor_name[MPI_MAX_PROCESS MPI_Comm_spawn(worker_program,
OR_NAME], MPI_ARGV_NULL,universe_size-1,
worker_program[100]; hostinfo,0,MPI_COMM_SELF,&family_
int rank_from_child,ich; comm, MPI_ERRCODES_IGNORE);
MPI_Status status; MPI_Intercomm_merge(family_comm,1,
MPI_Info hostinfo; &allcomm);
MPI_Init(&argc,&argv); MPI_Comm_rank(allcomm, &globalrank);
MPI_Comm_rank(MPI_COMM_WORLD, printf("manager: global rank is %d,rank is
&rank); %d \n",globalrank,rank);
MPI_Comm_size(MPI_COMM_WORLD, MPI_Allreduce(&globalrank,&sumrank,1,
&size); MPI_INT,MPI_SUM,allcomm);
MPI_Get_processor_name(processor_name printf("sumrank after allreduce on process
,&namelen); %d is %d \n", rank,sumrank);
MPI_Get_version(&version,&subversion); MPI_Comm_disconnect(&family_comm);
printf("I'm manager %d of %d on %s running MPI_Finalize();
MPI %d.%d\n", rank, size, return 0;
processor_name, version, subversion); }
if (size != 1) printf("Error: Only one manager
process should be running, but %d
were started.\n", size);
Codul programului HB_Woker_Comunication_V1.cpp care va fi
executat de procesele generate.
#include <stdio.h> MPI_Get_processor_name(processor_name
#include <stdlib.h> ,&namelen);
#include <mpi.h> MPI_Get_version(&version,&subversion);
int main(int argc, char** argv) printf("I'm worker %d of %d on %s running
{ MPI %d.%d\n", rank, size,
int rank, size, namelen, version, subversion, processor_name, version, subversion);
psize; MPI_Comm_get_parent(&parent);
int parent_rank; if (parent == MPI_COMM_NULL)
int globalrank,sumrank; {
MPI_Comm parent,allcom; printf("Error: no parent process found!\n");
char exit(1);
processor_name[MPI_MAX_PROCESS }
OR_ MPI_Comm_remote_size(parent,&psize);
NAME]; if
MPI_Init(&argc, &argv); (psize!=1)
MPI_Comm_rank(MPI_COMM_WORLD, {
&rank); printf("Error: number of parents (%d) should
MPI_Comm_size(MPI_COMM_WORLD, be 1.\n", psize);
&size); exit(2);
}
96
MPI_Intercomm_merge(parent,1,&allcom); printf("sumrank after allreduce on process
MPI_Comm_rank(allcom, &globalrank); %d is %d \n", rank,sumrank);
printf("worker: globalrank is %d,rank is %d MPI_Comm_disconnect(&parent);
\n",globalrank, rank); MPI_Finalize();
MPI_Allreduce(&globalrank,&sumrank,1, return 0;
MPI_INT,MPI_SUM,allcom); }

Rezultatele posibile ale executrii programului:


[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o Exemplu _3_7_4.exe Exemplu _3_7_4.cpp
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o HB_Spawn_Comunication_V1.exe
HB_Spawn_Comunication_V1.cpp
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpirun -n 1 -machinefile ~/nodes Exemplu _3_7_4.exe
I'm manager 0 of 1 on compute-0-0.local running MPI 2.1
Spawning 11 worker processes running ./HB_Woker_Comunication_V1.exe
I'm worker 0 of 11 on compute-0-3.local running MPI 2.1
I'm worker 3 of 11 on compute-0-3.local running MPI 2.1
I'm worker 6 of 11 on compute-0-3.local running MPI 2.1
I'm worker 9 of 11 on compute-0-3.local running MPI 2.1
I'm worker 8 of 11 on compute-0-0.local running MPI 2.1
I'm worker 1 of 11 on compute-0-1.local running MPI 2.1
I'm worker 2 of 11 on compute-0-0.local running MPI 2.1
I'm worker 4 of 11 on compute-0-1.local running MPI 2.1
I'm worker 5 of 11 on compute-0-0.local running MPI 2.1
I'm worker 7 of 11 on compute-0-1.local running MPI 2.1
I'm worker 10 of 11 on compute-0-1.local running MPI 2.1
manager: global rank is 0,rank is 0
worker: globalrank is 9,rank is 8
worker: globalrank is 3,rank is 2
worker: globalrank is 6,rank is 5
worker: globalrank is 2,rank is 1
worker: globalrank is 5,rank is 4
worker: globalrank is 8,rank is 7
worker: globalrank is 11,rank is 10
worker: globalrank is 10,rank is 9
worker: globalrank is 1,rank is 0
worker: globalrank is 4,rank is 3
worker: globalrank is 7,rank is 6
sumrank after allreduce on process 6 is 66
sumrank after allreduce on process 1 is 66
sumrank after allreduce on process 10 is 66
sumrank after allreduce on process 2 is 66
sumrank after allreduce on process 8 is 66
sumrank after allreduce on process 5 is 66
sumrank after allreduce on process 9 is 66
sumrank after allreduce on process 0 is 66
sumrank after allreduce on process 0 is 66
sumrank after allreduce on process 3 is 66
sumrank after allreduce on process 7 is 66
sumrank after allreduce on process 4 is 66
[Hancu_B_S@hpc Finale]$

97
3.7.3 Exerciii
1. S se elaboreze i s se execute pe clusterul USM un program MPI n
limbajul C++, prin care s se verifice dac un process fiu poate, la
rndul su, s genereze procese MPI.
2. Care este criteriul de verificare dac au fost sau nu generate procesele de
ctre procesul printe?
3. Ce mediu de comunicare trebuie utilizat ntre procesul printe i
procesele fiu pentru ca s nu existe dou procese cu rankul 0?
4. S se elaboreze i s se execute pe clusterul USM un program MPI n
limbajul C++, prin care s se verifice dac procesul printe i
procesele fiu pot utiliza aceeai operaie de reducere.
5. Fie dat un ir de vectori de lungimea n. S se elaboreze i s se
execute pe clusterul USM un program MPI n limbajul C++ n care:
a. procesul printe genereaz dinamic un numr l de procese,
iniializeaz vectorii , =
1, i trimite procesului fiu
vectorul ;
b. fiecare proces generat calculeaz norma vectorului , adic
= =1 2 , dup ce o trimite procesului printe;
c. procesul printe determin vectorul cu valoarea minimal a
normei.

3.8 Tipuri de date MPI

3.8.1 Harta tipului de date


O caracteristic important a MPI este includerea unui argument
referitor la tipul datelor transmise/recepionate. MPI are o mulime bogat
de tipuri predefinite: toate tipurile de baz din C++ (i din FORTRAN),
plus MPI_BYTE i MPI_PACKED. De asemenea, MPI furnizeaz
constructori pentru tipuri derivate i mecanisme pentru descrierea unui tip
general de date. n cazul general, mesajele pot conine valori de tipuri
diferite, care ocup zone de memorie de lungimi diferite i ne-contigue. n
MPI un tip de date este un obiect care specific o secven de tipuri de baz
i deplasrile asociate acestora, relative la tamponul de comunicare pe care

98
tipul l descrie. O astfel de secven de perechi (tip,deplasare) se
numete harta tipului. Secvena tipurilor (ignornd deplasrile) formeaz
semntura tipului general.
Typemap = {(type0 , disp0), . . . , (typen-1, dispn-1)}
Typesig = {type0, . . . , typen-1}
Fie c ntr-un program sunt declarate variabilele
float a,b;
int n;
Schematic presupunem c variabilele sunt stocate n memorie astfel:
a b n

Atunci harta noului tip de date creat n baza tipurilor indicate va fi


{(MPI_FLOAT, 0), (MPI_FLOAT, 4), (MPI_INT, 10)}
Harta tipului mpreun cu o adres de baz buf descriu complet un
tampon de comunicare i:
acesta are n intrri;
fiecare intrare i are tipul typei i ncepe la adresa buf+ dispi.
Vom face urmtoarea observaie: ordinea perechilor n Typemap nu
trebuie s coincid cu ordinea valorilor din tamponul de comunicare.
Putem asocia un titlu (handle) unui tip general i astfel folosim acest
titlu n operaiile de transmitere/recepie, pentru a specifica tipul datelor
comunicate. Tipurile de baz sunt cazuri particulare, predefinite. De
exemplu, MPI_INT are harta {(int, 0)}, cu o intrare de tip int i cu
deplasament zero. Pentru a nelege modul n care MPI asambleaz datele,
se utilizeaz noiunea de extindere (extent). Extinderea unui tip este spaiul
dintre primul i ultimul octet ocupat de intrrile tipului, rotunjit superior din
motive de aliniere. De exemplu, Typemap = {(double, 0), (char, 8)} are
extinderea 16, dac valorile double trebuie aliniate la adrese multiple de 8.
Deci, chiar dac dimensiunea tipului este 9, din motive de aliniere, o nou
valoare ncepe la o distan de 16 octei de nceputul valorii precedente.
Extinderea i dimensiunea unui tip de date pot fi aflate prin apelurile
funciilor MPI_Type_extent i MPI_Type_size. Prototipul n
limbajul C++ a acestor funcii este

99
int MPI_Type_extent(MPI_Datatype datatype,
MPI_Aint *extent)
unde
IN datatype numele tipului de date;
OUT extent extinderea tipului de date;

int MPI_Type_size(MPI_Datatype datatype, int


*size);
unde
IN datatype numele tipului de date;
OUT size dimensiunea tipului de date.

Deplasrile sunt relative la o anumit adres iniial de tampon. Ele


pot fi substituite prin adrese absolute, care reprezint deplasri relative la
adresa zero simbolizat de constanta MPI_BOTTOM. Dac se folosesc
adrese absolute, argumentul buf din operaiile de transfer trebuie s capete
valoarea MPI_BOTTOM. Adresa absolut a unei locaii de memorie se afl
prin funcia MPI_Address. Prototipul n limbajul C++ a acestei funcii
este
int MPI_Address(void *location, MPI_Aint
*address);
unde
IN location locaia de memorie (numele variabilei);
OUT address adresa.
Aici MPI_Aint este un tip ntreg care poate reprezenta o adres
oarecare (de obicei este int). Funcia MPI_Address reprezint un mijloc
comod de aflare a deplasrilor, chiar dac nu se folosete adresarea
absolut n operaiile de comunicare.

3.8.2 Tipuri derivate de date


MPI prevede diferite funcii care servesc drept constructori de tipuri
derivate de date. Utilizarea tipurilor noi de date va permite extinderea
modalitilor de transmitere/recepionare a mesajelor n MPI. Pentru
generarea unui nou tip de date (cum s-a menionat mai sus) este nevoie de
urmtoarea informaie:

100
numrul de elemente care vor forma noul tip de date;
o list de tipuri de date deja existente sau prestabilite;
adresa relativ din memorie a elementelor.
Funcia MPI_Type_contiguous
Este cel mai simplu constructor care produce un nou tip de date fcnd
mai multe copii ale unui tip existent, cu deplasri care sunt multipli ai
extensiei tipului vechi. Prototipul n limbajul C++ a acestei funcii este
int MPI_Type_contiguous(int count, MPI_Datatype
oldtype, MPI_Datatype *newtype)
unde
IN count numrul de copii (replicri);
IN oldtype tipul vechi de date;
OUT newtype tipul nou de date.
De exemplu, dac oldtype are harta {(int, 0),(double, 8)}, atunci noul
tip creat prin MPI_Type_contiguous(2,oldtype,&newtype) are
harta {(int, 0) , (double, 8), (int, 16) , (double, 24)} .
Noul tip n mod obligatoriu trebuie ncredinat sistemului nainte de a
fi utilizat n operaiile de transmitere-recepionare. Acest lucru se face n
baza funciei
int MPI_Type_commit(&newtype).
Cnd un tip de date nu mai este folosit, el trebuie eliberat. Aceasta se
face utiliznd funcia
int MPI_Type_free (&newtype);
Utilizarea tipului contiguu este echivalent cu folosirea unui contor
mai mare ca 1 n operaiile de transfer. Astfel apelul:
MPI_Send (buffer, count, datatype, dest, tag, comm);
este similar cu:
MPI_Type_contiguous (count, datatype, &newtype);
MPI_Type_commit (&newtype);
MPI_Send (buffer, 1, newtype, dest, tag, comm);
MPI_Type_free (&newtype);

Funcia MPI_Type_vector

101
Acest funcie permite specificarea unor date situate n zone
necontigue de memorie. Elementele tipului vechi pot fi separate ntre ele de
spaii avnd lungimea egal cu un multiplu al extinderii tipului (deci cu un
pas constant). Prototipul n limbajul C++ a acestei funcii este
int MPI_Type_vector(int count, int blocklength,
int stride,MPI_Datatype oldtype,
MPI_Datatype *newtype)
unde
IN count numrul de blocuri de date;
IN numrul de elemente n fiecare bloc de
blocklength date;
IN stride pasul, deci numrul de elemente ntre
nceputurile a dou blocuri vecine;
IN oldtype vechiul tip de date;
OUT newtype tipul nou de date.

Pentru exemplificare, s considerm o matrice a de 5 linii i 7 coloane


(cu elementele de tip float memorate pe linii). Pentru a construi tipul de
date corespunztor unei coloane folosim funcia:
MPI_Type_vector (5, 1, 7, MPI_FLOAT, &columntype);
unde
5 este numrul de blocuri;
1 este numrul de elemente din fiecare bloc (n cazul n care acest
numr este mai mare dect 1, un bloc se obine prin concatenarea
numrului respectiv de copii ale tipului vechi);
7 este pasul, deci numrul de elemente ntre nceputurile a dou
blocuri vecine;
MPI_FLOAT este vechiul tip.
Atunci pentru a transmite coloana a treia a matricii se poate folosi
funcia
MPI_Send (&a[0][2], 1, colunmtype, dest, tag, comm);

Funcia MPI_Type_indexed
Aceast funcie se utilizeaz pentru generarea unui tip de date pentru
care fiecare bloc are un numr particular de copii ale tipului vechi i un
deplasament diferit de ale celorlalte. Deplasamentele sunt date n multipli ai
extinderii vechiului tip. Utilizatorul trebuie s specifice ca argumente ale
102
constructorului de tip un tablou de numere de elemente per bloc i un
tablou de deplasri ale blocurilor. Prototipul n limbajul C++ al acestei
funcii este
int MPI_Type_indexed(int count, int
*array_of_blocklengths, int
*array_of_displacements, MPI_Datatype
oldtype, MPI_Datatype *newtype)
unde
IN count numrul de blocuri de date;
IN numrul de elemente pentru
array_of_blocklengths fiecare bloc de date;
IN pasul pentru fiecare bloc (n
array_of_displacements octei),
IN oldtype vechiul tip de date;
OUT newtype tipul nou de date.

Pentru exemplificare, s considerm o matrice a de 4 linii i 4 coloane


(cu elementele de tip int memorate pe linii). Pentru a construi tipul de
date corespunztor diagonalei principale folosim funcia:
MPI_Type_ indexed(4, block_lengths, relloc, MPI_INT, &diagtype);
unde block_lengths={1, 1, 1} i relloc={4, 4, 4, 4}.

Funcia MPI_Type_struct
Aceast funcie este o generalizare a funciilor precedente prin aceea
c permite s genereze tipuri de date pentru care fiecare bloc s constea din
replici ale unor tipuri de date diferite. Prototipul n limbajul C++ al acestei
funcii este
int MPI_Type_struct(int count, int
array_of_blocklengths[],MPI_Aint
array_of_displacements[],MPI_Datatype
array_of_types[], MPI_Datatype *newtype)
unde
IN count numrul de blocuri de date;
IN array_of_blocklengths numrul de elemente pentru
fiecare bloc de date;
IN pasul pentru fiecare bloc (n
array_of_displacements octei);
103
IN array_of_types vechiul tip de date pentru fiecare
bloc;
OUT newtype tipul nou de date.

Vom ilustra utilizarea funciilor de generare a tipurilor noi de date


pentru realizarea operaiilor de trimitere/recepionare prin urmtorul
exemplu.
Exemplul 3.8.1 S se elaboreze un program MPI n limbajul C++
pentru generarea noilor tipuri de date i utilizarea lor la operaiile de
trimitere/recepionare.
Indicaie. Tipul vechi de date este o structur din dou elemente care
indic poziia n spaiu i masa unei particule.
Mai jos este prezentat codul programului n limbajul C++ n care se
realizeaz cele indicate n exemplul 3.8.1.
#include "mpi.h" MPI_Init(&argc,&argv);
#include <stdio.h> MPI_Comm_size(MPI_COMM_WORLD,
#include <math.h> &nProc);
typedef struct MPI_Comm_rank(MPI_COMM_WORLD,
{ &myRank);
float position[3]; construct_datatypes();
float mass; if (myRank ==0)
} Particle; printf("\n=====REZULTATUL PROGRAMULUI
MPI_Datatype MPI_Particle; '%s' \n",argv[0]);
void construct_datatypes(void) MPI_Barrier(MPI_COMM_WORLD);
{ myP=(Particle*)calloc(nProc,sizeof(Particle))
Particle p; ;
int blens[2]; if(myRank == 0){
MPI_Aint displ[2]; for(i=0;i<nProc;i++){
MPI_Datatype types[2]; myP[i].position[0]=i;myP[i].position[1]=i+1;
blens[0]=3; types[0]=MPI_FLOAT; myP[i].position[2]=i+2;
displ[0]=(MPI_Aint)&p.position- myP[i].mass=10+100.0*rand()/RAND_MAX;
(MPI_Aint)&p; }
blens[1]=1; types[1]=MPI_FLOAT; }
displ[1]=(MPI_Aint)&p.mass- MPI_Bcast(myP,nProc,MPI_Particle,0,
(MPI_Aint)&p; MPI_COMM_WORLD);
MPI_Type_struct(2,blens,displ,types, printf("Proces rank %d: pozitia particuleei
&MPI_Particle); (%f, %f, %f) masa ei %f\n", myRank,
MPI_Type_commit(&MPI_Particle); myP[myRank].position[0],
return; myP[myRank].position[1],
} myP[myRank].position[2],
int main(int argc, char *argv[]) myP[myRank].mass);
{ MPI_Finalize();
int nProc,myRank,i; return 0;
Particle *myP; }
Rezultatele posibile ale executrii programului:
104
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o Exemplu_3_8_1.exe Exemplu_3_8_1.cpp
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpirun -n 16 -machinefile ~/nodes4 Exemplu_3_8_1.exe

=====REZULTATUL PROGRAMULUI 'Exemplu_3_8_1.exe'


Proces rank 0: pozitia particuleei (0.000000, 1.000000, 2.000000) masa ei 94.018768
Proces rank 2: pozitia particuleei (2.000000, 3.000000, 4.000000) masa ei 88.309921
Proces rank 4: pozitia particuleei (4.000000, 5.000000, 6.000000) masa ei 101.164734
Proces rank 12: pozitia particuleei (12.000000, 13.000000, 14.000000) masa ei 46.478447
Proces rank 8: pozitia particuleei (8.000000, 9.000000, 10.000000) masa ei 37.777470
Proces rank 6: pozitia particuleei (6.000000, 7.000000, 8.000000) masa ei 43.522274
Proces rank 14: pozitia particuleei (14.000000, 15.000000, 16.000000) masa ei 105.222969
Proces rank 10: pozitia particuleei (10.000000, 11.000000, 12.000000) masa ei 57.739704
Proces rank 1: pozitia particuleei (1.000000, 2.000000, 3.000000) masa ei 49.438293
Proces rank 3: pozitia particuleei (3.000000, 4.000000, 5.000000) masa ei 89.844002
Proces rank 5: pozitia particuleei (5.000000, 6.000000, 7.000000) masa ei 29.755136
Proces rank 15: pozitia particuleei (15.000000, 16.000000, 17.000000) masa ei 101.619507
Proces rank 11: pozitia particuleei (11.000000, 12.000000, 13.000000) masa ei 72.887093
Proces rank 7: pozitia particuleei (7.000000, 8.000000, 9.000000) masa ei 86.822960
Proces rank 13: pozitia particuleei (13.000000, 14.000000, 15.000000) masa ei 61.340092
Proces rank 9: pozitia particuleei (9.000000, 10.000000, 11.000000) masa ei 65.396996
[Hancu_B_S@hpc]$

3.8.3 Exerciii
1. S se elaboreze i s se execute pe clusterul USM un program MPI n
limbajul C++ n care se creeaz un tip de date care reprezint o linie a
unui masiv i se distribuie o linie diferit tuturor proceselor
comunicatorului MPI_COMM_WORLD.
2. S se elaboreze i s se execute pe clusterul USM un program MPI n
limbajul C++ n care procesul cu rankul 0 recepioneaz de la toate
procesele comunicatorului MPI_COMM_WORLD date de tip structur
care const din rankul procesului i numele nodului pe care procesul
este executat.
3. S se elaboreze i s se execute pe clusterul USM un program MPI n
limbajul C++ n care se construiete o matrice transpus utiliznd
proceduri de generare a tipurilor de date.
4. Fie dat o matrice ptratic de orice dimensiune. S se creeze
urmtoarele 3 tipuri de date:
elementele de pe diagonala principal;
elementele de pe diagonala secundar de jos;
elementele de pe diagonala secundar de sus.
Matricea este iniializat de procesul cu rankul 0 i prin funcia
MPI_Brodcast se transmit aceste tipuri de date tuturor proceselor.

105
5. S se elaboreze i s se execute pe clusterul USM un program MPI n
limbajul C++ n care s se copieze submatricea triunghiular de jos a
matricei A n submatricea triunghiular de jos a matricei B utiliznd
funcia MPI_Type_indexed.
6. Fie dat o matrice A aij i 1, m care este divizat n blocuri Akp de
j 1, n
dimensiunea mk n p . S se elaboreze i s se execute pe clusterul USM
un program MPI n limbajul C++ n care se creeaz un nou tip de date
corespunztor submatricei Akp i procesul cu rankul 0 transmite acest
tip de date procesului cu rankul k p .

3.9 Utilizarea fiierelor n MPI

3.9.1 Operaiile de intrare/ieire (I/O) n programe MPI


n acest paragraf vom analiza urmtoarele modaliti de utilizare a
fiierelor n programarea paralel.
1. Utilizarea neparalel a fiierelor procesul root recepioneaz datele
de la procese utiliznd funciile MPI de transmitere/recepionare a
mesajelor i apoi le scrie/citete n fiier. Acest mod schematic poate fi
reprezentat astfel:

root Memorie

Procese

Fiier

Aceast modalitate de utilizare a fiierelor poate fi exemplificat prin


urmtorul cod de program:
#include "mpi.h"
#include <stdio.h>
#define BUFSIZE 100
int main(int argc, char *argv[]){
int i, myrank, numprocs, buf[BUFSIZE];
106
MPI_Status status;
FILE *myfile;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &myrank);
MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
for (i=0; i<BUFSIZE; i++)
buf[i] = myrank * BUFSIZE + i;
if (myrank != 0)
MPI_Send(buf, BUFSIZE, MPI_INT, 0, 99, MPI_COMM_WORLD);
else {
myfile = fopen("testfile", "w");
fwrite(buf, sizeof(int), BUFSIZE, myfile);
for (i=1; i<numprocs; i++) {
MPI_Recv(buf, BUFSIZE, MPI_INT, i, 99, MPI_COMM_WORLD,
&status);
fwrite(buf, sizeof(int), BUFSIZE, myfile);
}
fclose(myfile);
}
MPI_Finalize();
return 0;
}

2. Fiecare proces utilizeaz n paralel fiierul su propriu. Acest mod


schematic poate fi reprezentat astfel:

Memorie
Procese

Fiierul

Aceast modalitate de utilizare a fiierelor poate fi exemplificat prin


urmtorul cod de program:
#include "mpi.h"
#include "mpi.h"
#include <stdio.h>
#define BUFSIZE 100
int main(int argc, char *argv[])
{
int i, myrank, buf[BUFSIZE];
char filename[128];
FILE *myfile;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &myrank);
for (i=0; i<BUFSIZE; i++)
buf[i] = myrank * BUFSIZE + i;
sprintf(filename, "testfile.%d", myrank);
myfile = fopen(filename, "w");
107
fwrite(buf, sizeof(int), BUFSIZE, myfile);
fclose(myfile);
MPI_Finalize();
return 0;
}

3. Utilizarea paralel de ctre toate procesele unui mediu de comunicare


a unui i acelai fiier. Acest mod schematic poate fi reprezentat astfel:

Memorie
Procese

Fiier

n acest paragraf vom studia detaliat modul 3) de utilizare a fiierelor.


La nceput vom defini urmtoarele noiuni:
E-type (tip elementar de date) unitate de acces la date i de
poziionare.
Acest tip poate fi un tip predefinit n MPI sau un tip derivat de date.
F-type (tip fiier) servete drept baz pentru partiionarea fiierului
n mediul de procese, definete un ablon de acces la fiier. Reprezint un
ir de e-tip-uri sau tipuri derivate de date MPI.
Vedere fiier (file view) un set de date vizibile i accesibile dintr-un
fiier deschis ca un set ordonat de e-tipuri. Fiecare proces are propria sa
vedere fiier specificat de urmtorii trei parametri: offset, e-tip i f-tip.
ablonul descris de f-tip se repet ncepnd cu poziia offset.
Offset aceasta este poziia n fiier n raport cu vederea curent,
prezent ca un numru de e-tip-uri. Gurile n f-tipe sunt omise la
calcularea numrului poziiei. Zero este poziia primului e-tip vizibil n
vederea fiierului.
Referine de fiier individuale referine de fiier care sunt locale
pentru fiecare proces ntr-un fiier deschis.
Referine de fiier comune referine de fiier utilizate n acelai
timp de un grup de procese pentru care este deschis fiierul.
Vom reprezenta grafic noiunile definite mai sus.
e-tip

108
0 f-tip-ul pentru procesul 0
1 1 f-tip-ul pentru procesul 1
2 2 f-tip-ul pentru procesul 2
3 f-tip-ul pentru procesul 3

Placarea (tiling) fiierului


0 1 1 2 2 3 0 1 1 2 2 3 0 1 1 2 2 3 ....

Astfel fiecare proces va vedea i deci va avea acces la urmtoarele


date:

0 0 0 ... Procesul 0

1 1 1 1 1 ... Procesul 1

2 2 2 2 2 ... Procesul 2

3 3 3 ... Procesul 3

Vom descrie algoritmul de baz pentru utilizarea fiierelor n


programe MPI.
1. Definirea variabilelor i tipurilor necesare de date pentru
construirea e-tip-urilor i a f-tip-urilor.
2. Deschiderea unui fiier (funcia MPI_File_open).
3. Determinarea pentru fiecare proces vederea fiier (funcia
MPI_File_set_view).
4. Citire/scriere de date (funciile MPI_File_write,
MPI_File_read).
5. nchidere fiier (funcia MPI_File_close).

3.9.2 Funciile MPI pentru utilizarea fiierelor


n acest paragraf vom prezenta funciile de baz pentru utilizarea
fiierelor n programe MPI.
Funcia MPI_File_open

109
int MPI_File_open(MPI_Comm comm, char
*filename,int amode, MPI_Info info, MPI_File
*fh)
unde
IN comm nume comunicator;
IN filename numele fiierului;
IN amode tipul de operaii care se pot
executa asupra fiierului;
IN info obiect informaional;
OUT fh descriptorul de fiier.
Valorile posibile ale parametrului amode:
MPI_MODE_RDONLY accesibil numai la citire;
MPI_MODE_RDWR accesibil la citire i nscriere;
MPI_MODE_WRONLY accesibil numai la nscriere;
MPI_MODE_CREATE creare fiier, dac nu exist;
MPI_MODE_EXCL eroare, dac fiierul creat deja exist;
MPI_MODE_DELETE_ON_CLOSE distrugere fiier la nchidere;
MPI_MODE_UNIQUE_OPEN fiierul nu se va deschide i de alte
procese;
MPI_MODE_SEQUENTIAL acces secvenial la datele din fiier;
MPI_MODE_APPEND indicarea poziiei iniiale a parametrului offset la
sfritul fiierului.
La fel se pot utiliza i combinaii logice ae diferitor valori.

Funcia MPI_File_set_view
int MPI_File_set_view(MPI_File fh, MPI_Offset
disp, MPI_Datatype etype, MPI_Datatype
filetype, char *datarep, MPI_Info info)
unde
IN/OUT fh descriptorul de fiier;
IN disp valoarea deplasrii;
IN etype tipul elementar de date;
IN filetype tipul fiier de date;
IN datarep modul de reprezentare a datelor din fiier;
IN info obiect informaional.

110
Valorile parametrului datarep pot fi native, internal,
external32 sau definite de utilizator.
Funcia MPI_File_read
int MPI_File_read(MPI_File fh, void *buf, int
count, MPI_Datatype datatype, MPI_Status
*status)
unde
IN/OUT fh descriptorul de fiier;
OUT buf adresa de start a tamponului de date;
IN count numrul de elemente din tamponul de date;
IN datatype tipul de date din tamponul buf;
OUT status obiectul de stare.

Procesul MPI care face apel la aceast funcie va citi count


elemente de tip datatype din fiierul fh, n conformitate cu vederea la
fiier fixat de funcia MPI_File_set_view, i le va memora n
variabila buf.

Funcia MPI_File_write
int MPI_File_write(MPI_File fh, void *buf, int
count, MPI_Datatype datatype, MPI_Status
*status)
unde
IN/OUT fh descriptorul de fiier;
IN buf adresa de start a tamponului de date;
IN count numrul de elemente din tamponul de date;
IN datatype tipul de date din tamponul buf;
OUT status obiectul de stare.

Procesul MPI care face apel la aceast funcie va nscrie count


elemente de tip datatype din variabila buf n fiierul fh, n
conformitate cu vederea la fiier fixat de funcia
MPI_File_set_view.
n tabelul de mai jos vom prezenta rutinele MPI pentru operaiile de
citire/sciere a datelor din/n fiier.

111
Mod de Mod de Mod de coordonare
poziio- sincroni noncolectiv colectiv
nare zare
Explicit cu MPI_File_read_at MPI_File_read_at_all
offset blocare MPI_File_write_at MPI_File_write_at_all
cu MPI_File_iread_at MPI_File_read_at_all_begin
nonblo- MPI_File_iwrite_at MPI_File_read_at_all_end
care MPI_File_write_at_all_begin
MPI_File_write_at_all_end
Poziio- cu MPI_File_read MPI_File_read_all
nare blocare MPI_File_write MPI_File_write_all
individu cu MPI_File_iread MPI_File_read_all_begin
-al nonblo- MPI_File_iwrite MPI_File_read_all_end
care MPI_File_write_all_begin
MPI_File_write_all_end
Poziio- cu MPI_File_read_shared MPI_File_read_ordered
nare blocare MPI_File_write_shared MPI_File_write_ordered
colecti- cu MPI_File_iread_shared MPI_File_read_ordered
v nonblo- MPI_File_iwrite_shared _begin
care MPI_File_read_ordered_end
MPI_File_write_ordered
_begin
MPI_File_write_ordered_end

Vom ilustra utilizarea funciilor MPI pentru realizarea operaiilor de


citire/scriere a datelor din/n fiier prin urmtorul exemplu
Exemplul 3.9.1 Fie dat o matrice de dimensiunea 4x8. S se
elaboreze un program MPI n limbajul C++ care va realiza urmtoarele:
se creeaz un fiier i procesul 0 scrie n el primele dou rnduri
ale matricei, procesul 1 scrie n el linia a treia a matricei, i la
rndul su, procesul 2 scrie n el rndul al patrulea al matricei;
procesul 3 citete primele dou rnduri ale matricei din fiierul
creat i le tiprete, corespunztor, procesul 4 (procesul 5) citete
rndul trei (patru) al matricei din fiierul creat i le tiprete.
Indicaie. Rndurile matricei sunt iniializate de procesele
corespunztoare.
Mai jos este prezentat codul programului n limbajul C++ n care se
realizeaz cele indicate n exemplul 3.9.1.
#include <mpi.h> MPI_File OUT;
#include <stdio.h> MPI_Aint rowsize;
int main(int argc,char**argv) MPI_Datatype etype,ftype0,ftype1,ftype2;
{ MPI_Status state;
int rank,count,amode,i,j; MPI_Datatype MPI_ROW;
112
int blengths[2]={0,1}; {
MPI_Datatype types[2]; for(int j = 0; j<8; j++)
MPI_Aint disps[2];
MPI_Init(&argc,&argv); printf("%d\t", value0[i][j]);
MPI_Comm_rank(MPI_COMM_WORLD,&ra printf("\n");
nk); }
amode=MPI_MODE_DELETE_ON_CLOSE; }
MPI_File_open(MPI_COMM_WORLD, if (rank==1)
"array.dat",amode,MPI_INFO_NULL,
&OUT); {
MPI_File_close(&OUT); int value1[8];
amode=MPI_MODE_CREATE|MPI_MODE_R for (j=1;j<=8;++j)
DWR; value1[j-1]= 10*3+j;
MPI_File_open(MPI_COMM_WORLD,"array.
dat",amode,MPI_INFO_NULL,&OUT); MPI_File_set_view(OUT,0,etype,
MPI_Type_contiguous(8,MPI_INT, ftype1,"native",MPI_INFO_NULL);
&MPI_ROW); MPI_File_write(OUT,&value1,1,
MPI_Type_commit(&MPI_ROW); MPI_ROW,&state);
etype=MPI_ROW; MPI_Get_count(&state,MPI_ROW,
ftype0=MPI_ROW;
types[0]=types[1]=MPI_ROW; &count);
disps[0]=(MPI_Aint) 0; printf("===Procesul %d a inscris in fisierul
MPI_Type_extent(MPI_ROW,&rowsize); ""array.dat"" urmatoarle %d
disps[1]=2*rowsize; randuri\n",rank,count);
MPI_Type_struct(2,blengths,disps,types,
&ftype1); for(int j = 0; j<8; j++)
MPI_Type_commit(&ftype1); {
disps[1]=3*rowsize; printf("%d\t", value1[j]);
MPI_Type_struct(2,blengths,disps,types, }
&ftype2); printf("\n");
MPI_Type_commit(&ftype2);
count=0; }
// Inscrierea in paralel a datelor in fisierul if (rank==2)
"array.dat" {
if (rank==0) int value2[8];
{
int value0[2][8]; for (j=1;j<=8;++j)
for (i=1;i<=2;++i) value2[j-1]= 10*4+j;
for (j=1;j<=8;++j) MPI_File_set_view(OUT,0,etype,
value0[i-1][j-1]= 10*i+j; ftype2,"native",MPI_INFO_NULL);
MPI_File_set_view(OUT,0,etype,ftype0, MPI_File_write(OUT,&value2,1,
"native",MPI_INFO_NULL); MPI_ROW,&state);
MI_File_write(OUT,&value0[rank][0],2, MPI_Get_count(&state,MPI_ROW,
MPI_ROW,&state); &count);
MPI_Get_count(&state,MPI_ROW, printf("===Procesul %d a inscris in fisierul
&count); ""array.dat"" urmatoarele %d
printf("===Procesul %d a inscris in fisierul randuri\n",rank,count);
""array.dat"" urmatoarele %d for(int j = 0; j<8; j++)
randuri:\n",rank,count); {
for(int i = 0; i<2; i++) printf("%d\t", value2[j]);
113
} MPI_Get_count(&state,MPI_ROW,
printf("\n"); &count);
} printf("---Procesul %d a citit din fisierul
// Citirea in paralel a datelor din fisierul ""array.dat"" urmatoarele %d
"array.dat" rinduri\n",rank,count);
if (rank==3) for(int j = 0; j<8; j++)
{ {
int myval3[2][8]; printf("%d\t", myval4[j]);
MPI_File_set_view(OUT,0,etype, }
ftype0,"native",MPI_INFO_NULL); printf("\n");
MPI_File_read(OUT,&myval3[0][0], }
2,MPI_ROW,&state); if (rank==5)
MPI_Get_count(&state,MPI_ROW, {
&count); int myval5[8];
printf("---Procesul %d a citit din fisierul MPI_File_set_view(OUT,0,etype,
""array.dat"" urmatoarele %d ftype2,"native",MPI_INFO_NULL);
rinduri\n",rank,count); MPI_File_read(OUT,&myval5,1,
for(int i = 0; i<2; i++) MPI_ROW,&state);
{ MPI_Get_count(&state,MPI_ROW,
for(int j = 0; j<8; j++) &count);
printf("%d\t", myval3[i][j]); printf("---Procesul %d a citit din fisierul
printf("\n"); ""array.dat"" urmatoarele %d
} rinduri\n",rank,count);
} for(int j = 0; j<8; j++)
if (rank==4) {
{ printf("%d\t", myval5[j]);
int myval4[8]; }
MPI_File_set_view(OUT,0,etype, printf("\n");
ftype1,"native",MPI_INFO_NULL); }
MPI_File_read(OUT,&myval4,1, MPI_File_close(&OUT);
MPI_ROW,&state); MPI_Finalize();
}

Rezultatele posibile ale executrii programului:


[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpiCC -o Exemplu_3_9_1.exe Exemplu_3_9_1.cpp
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpirun -n 6 -machinefile ~/nodes6 Exemplu_3_9_1.exe
===Procesul 1 a inscris in fisierul array.dat urmatoarle 1 randuri
31 32 33 34 35 36 37 38
---Procesul 3 a citit din fisierul array.dat urmatoarele 2 rinduri
11 12 13 14 15 16 17 18
21 22 23 24 25 26 27 28
---Procesul 5 a citit din fisierul array.dat urmatoarele 1 rinduri
41 42 43 44 45 46 47 48
---Procesul 4 a citit din fisierul array.dat urmatoarele 1 rinduri

114
31 32 33 34 35 36 37 38
===Procesul 2 a inscris in fisierul array.dat urmatoarele 1 randuri
41 42 43 44 45 46 47 48
===Procesul 0 a inscris in fisierul array.dat urmatoarele 2 randuri:
11 12 13 14 15 16 17 18
21 22 23 24 25 26 27 28
[Hancu_B_S@]$ /opt/openmpi/bin/mpirun -n 2 -machinefile ~/nodes6 Exemplu_3_9_1.exe
===Procesul 0 a inscris in fisierul array.dat urmatoarele 2 randuri:
11 12 13 14 15 16 17 18
21 22 23 24 25 26 27 28
===Procesul 1 a inscris in fisierul array.dat urmatoarle 1 randuri
31 32 33 34 35 36 37 38
[Hancu_B_S@hpc]$ /opt/openmpi/bin/mpirun -n 4 -machinefile ~/nodes6 Exemplu_3_9_1.exe
===Procesul 1 a inscris in fisierul array.dat urmatoarle 1 randuri
31 32 33 34 35 36 37 38
===Procesul 0 a inscris in fisierul array.dat urmatoarele 2 randuri:
11 12 13 14 15 16 17 18
21 22 23 24 25 26 27 28
---Procesul 3 a citit din fisierul array.dat urmatoarele 2 rinduri
11 12 13 14 15 16 17 18
21 22 23 24 25 26 27 28
===Procesul 2 a inscris in fisierul array.dat urmatoarele 1 randuri
41 42 43 44 45 46 47 48
Fiierul array.dat
[Hancu_B_S@hpc]$ od -d array.dat
000000 11 0 12 0 13 0 14 0
0000020 15 0 16 0 17 0 18 0
0000040 21 0 22 0 23 0 24 0
0000060 25 0 26 0 27 0 28 0
0000100 31 0 32 0 33 0 34 0
0000120 35 0 36 0 37 0 38 0
0000140 41 0 42 0 43 0 44 0
0000160 45 0 46 0 47 0 48 0

3.9.3 Exerciii
1. Care sunt etapele principale pentru utilizarea fiierelor n programe
MPI?
2. S se elaboreze i s se execute pe clusterul USM un program MPI n
limbajul C++ n care se realizeaz condiiile enunate n exemplul 3.4.2
i procesul de distribuire a liniilor matricei este substituit prin operaiile
I/O, adic utilizarea fiierelor.
3. S se elaboreze i s se execute pe clusterul USM un program MPI n
limbajul C++ n care se realizeaz condiiile enunate n exemplul 3.4.3
i procesul cu rankul 0 citete dimensiunea i elementele matricei dintr-
un fiier. Numele fiierului se indic ca parametru utilitarului mpirun.
115
4. Fie dat o matrice A aij i 1, m care este divizat n blocuri Akp de
j 1, n
dimensiunea mk n p . S se elaboreze i s se execute pe clusterul USM
un program MPI n limbajul C++ n care fiecare proces cu rankul k p
din comunicatorul MPI_COMM_WORLD nscrie n acelai fiier
submatricea Akp . Procesul cu rankul 0 iniializeaz matricea A.
5. Fie dat un fiier care conine blocuri Akp de dimensiunea mk n p (a se
vedea exerciiul 4). S se elaboreze i s se execute pe clusterul USM un
program MPI n limbajul C++ n care fiecare proces cu rankul k p din
comunicatorul MPI_COMM_WORLD citete din acelai fiier submatricea
Akp . Procesul cu rankul 0 iniializeaz matricea A i culege de la
celelalte procese submatricele Akp dup ce tiprete matricea obinut.

116
Bibliografie
1. B. Wilkinson, M. Allen. Parallel Programming. Printice-Hall, 1999.
2. B. Dumitrescu. Algoritmi de calcul paralel. Bucureti, 2001.
3. Gh. Dodescu, B. Oancea, M. Raceanu. Procesare paralel. Bucureti:
Economica, 2002.
4. Gh.M. Panaitescu. Arhitecturi paralele de calcul. Ploieti:
Universitatea Petrol-Gaze, 2007.
5. R.W. Hockney, C.R. Jesshope. Calculatoare paralele: Arhitectur,
programare i algoritmi. Bucureti, 1991.
6. http://www.mpi-forum.org/docs/docs.html.
7. MPI-2. Extension to the Message-Passing Interface: http://www.mpi-
forum.org/docs/mpi20-html/
8. P.S. Pacheco. An Introduction to Parallel Programming. 2011. pp. 370.
www.mkp.com or www.elsevierdirect.com
9. Ph.M. Papadopoulos. Introduction to the Rocks Cluster Toolkit
Design and Scaling. San Diego Supercomputer Center, University of
California, San Diego, http://rocks.npaci.edu
10. .. , .. , .. .
. --:
, 2003.
11. .. .
MPI. , 2004.
12. .. , .. . O
.
: , 2003.
13. .. . . , 2000.
14. .. , .. .
MPI, 2003.
15. .. . .
http://www.software.unn.ac.ru/ccam/kurs1.htm
16.
. , 2010. http://hpc.tti.sfedu.ru
17. .. , .. , .. .

.
, 2012.

117
Anex

Vom prezenta o mostr de lucrare de laborator pentru implementarea


soft pe clusterul USM a unui algoritm paralel.
Lucrare de laborator. S se elaboreze un program MPI n limbajul
C++ pentru determinarea n paralel a mulimii tuturor situaiilor de
echilibru n strategii pure pentru un joc bimatriceal.
Fie dat un joc bimatriceal I , J , A, B unde I mulimea de indici ai
liniilor matricelor, J mulimea de indici ai coloanelor matricelor, iar
A aij iI , B bij iI reprezint matricele de ctig ale juctorilor.
j J j J


Situaia de echilibru este perechea de indici i , j , pentru care se
verific sistemul de inegaliti:
aij i I

a
i j
i , j
b bi j j J
i j
Vom spune c linia i strict domin linia k n matricea A dac i numai
dac > pentru orice . Dac exist j pentru care inegalitatea nu
este strict, atunci vom spune c linia i domin linia k. Similar, vom spune:
coloana j strict domin coloana l n matricea B dac i numai dac >
pentru orice . Dac exist i pentru care inegalitatea nu este strict,
atunci vom spune: coloana j domin coloana l.
Algoritmul de determinare a situaiei de echilibru
a) Eliminarea, n paralel, din matricea A i B a liniilor care sunt
dominate n matricea A i din matricea A i B a coloanelor care sunt
dominate n matricea B.
b) Se determin situaiile de echilibru pentru matricea A' , B ' ,
A' a 'ij iI ' i B ' b'ij iI ' obinut din pasul a). Este clar c
j J ' jJ '
I I si J J .
Pentru orice coloan fixat n matricea A notm (evideniem)
toate elementele maximale dup linie. Cu alte cuvinte, se
determin i* ( j ) arg max a'ij pentru orice . Pentru aceasta
iI '

118
se va folosi funcia MPI_Reduce i operaia ALLMAXLOC2 (a
se vedea exerciiul 4 din paragraful 3.4.4).
Pentru orice linie fixat n matricea B notm toate elementele
maximale de pe coloane. Cu alte cuvinte, se determin
j* (i) arg max b'ij pentru orice . Pentru aceasta se va folosi
jJ '
funcia MPI_Reduce i operaia ALLMAXLOC (a se vedea
Exerciiul 4 din paragraful 3.4.4).
Selectm acele perechi de indici care concomitent sunt selectate
att n matricea A ct i n matricea B. Altfel spus, se
i* i* ( j * )
determin * .
j j * (i* )
c) Se construiesc situaiile de echilibru pentru jocul cu matricele iniiale
A i B.
Vom analiza urmtoarele exemple.
Exemplul 1. Situaia de echilibru se determin numai n baza eliminrii
liniilor i a coloanelor dominate. Considerm urmtoarele matrici:
400 0 0 0 0 0

300 300 0 0 0 0
200 200 200 0 0 0
A .
100 100 100 100 0 0

0 0 0 0 0 0
100 100 100 100 100 100

0 200 100 0 100 200

0 0 100 0 100 200
0 0 0 0 100 200
B .
0 0 0 0 100 200

0 0 0 0 0 200
0 0 0
0 0 0

2
n cazul utilizrii operaiei MAXLOC rezultatele pot fi incorecte.
119
Vom elimina liniile i coloanele dominate n urmtoarea ordine: linia 5,
coloana 5, linia 4, coloana 4, coloana 3, linia 3, coloana 0, linia 0, coloana
1, linia 1. Astfel obinem matricele A 200 , B 0 i situaia de

echilibru este i * , j * 2,2 i ctigul juctorului 1 este 200, al juctorului
2 este 0.
Exemplul 2. Considerm urmtoarele matrice
2 0 1 1 0 2

A 1 2 0 , B 2 1 0 . n matricea A nu exist linii dominate, n
0 1 2 0 2 1

matricea B nu exist colane dominate. Pentru comoditate, vom reprezenta
2,1 0,0 1, 2

acest joc astfel: AB 1, 2 2,1 0,0 . Uor se observ c n acest joc
0,0 1, 2 2,1

nu exist situaii de echilibru n strategii pure.
j J
Exemplul 3. Considerm urmtoarele matrice: A aij ,
i I
j J
B bij ,
i I
unde aij c,bij k pentru orice i I , j J i orice constante c,k. Atunci
mulimea de situaii de echilibru este i, j : i I , j I .

Pentru realizarea acestui algoritm pe clustere paralele sunt


obligatorii urmtoarele:

1) Paralelizarea la nivel de date se realizeaz astfel:


a) Procesul cu rankul 0 iniializeaz valorile matricelor A i B. Dac
n>5, m>5, atunci procesul cu rankul 0 citete matricele dintr-un
fiier text.
b) Distribuirea matricelor pe procese se face astfel nct s se
realizeze principiul load balancing.
2) Paralelizarea la nivel de operaii se realizeaz i prin utilizarea funciei
MPI_Reduce i a operaiilor nou create.

120
Boris HNCU, Elena CALM

MODELE DE PROGRAMARE PARALEL PE CLUSTERE


PARTEA I. PROGRAMARE MPI

Note de curs

Redactare: Ana Ferafontov


Machetare computerizat:

Bun de tipar . Formatul


Coli de tipar . Coli editoriale .
Comanda 53. Tirajul 50 ex.

Centrul Editorial Poligrafic al USM


str. Mateevici, 60, Chiinu, MD-2009

121