Sunteți pe pagina 1din 73

PROGRAMAREA MULTICALCULATOARELOR

• OCCAM
• APLICATII PARALELE PE UN HIPERCUB
• MPI
• PVM
CARACTERISTICI ALE PROGRAMARII
MULTICALCULATOARELOR
-nu exista spatiu unic de adresa (programarea fara memorie partajata);
-programul paralel se executa ca o multime de procese concurente, numai cu
memorie locala;
-comunicatia intre procese se face prin transfer de mesaje (operatie „send”
dintr-un proces <-> operatie „receive” a altui proces);
-nu exista variabile partajate (fara mecanisme de protectie a accesului la
acestea).

Limbaje de programare in transfer de mesaje:


-limbaje secventiale (Pascal, C, Fortran) cu extensii pentru transmisia si
receptia mesajelor;
-limbaje de programare paralela speciale: Ada, Occam, Linda, Orca, SR,
Parlog, Emerald, etc.

Biblioteci de comunicatie:
-PVM: Parallel Virtual Machine;
-MPI: Message Passing Interface.
LIMBAJUL OCCAM

-realizat in 1986 de INMOS;

-bazat pe limbajul CSP (Communicating Sequential Processes) din 1978;

-algoritm paralel -> colectie de procese asincrone, care comunica prin canale;

-canalul = entitate abstracta de comunicatie, punct la punct, directionata,


sincrona si fara buffer intre doua procese paralele.
Procese fundamentale:

-asignare:
variabila := expresie

-citire dintr-un canal de comunicatie:


canal ? variabila

-scriere intr-un canal de comunicatie:


canal ! expresie

-orice alt proces = structura ierarhica de subprocese;


-subproces = colectie de procese mai simple executate in serie sau in paralel pe acelasi
transputer sau pe transputere diferite;
-procesele: create static (nu se pot crea dinamic) in momentul compilarii.
Operatori de constructie (constructori) pentru definirea proceselor complexe:

-SEQ creaza un proces complex dintr-un numar de procese minore care se vor
executa secvential:
SEQ
proces_1
proces_2
proces_3
........
proces_n

-PAR creaza un proces complex care lanseaza executia paralela a mai multor
procese:
PAR
proces_1
proces_2
proces_3
........
proces_n
-IF creaza un proces complex care lanseaza in executie unul din procesele
componente, corespunzand primei conditii adevarate (testarea se face in ordinea scrierii
conditiilor):

IF
conditie_1
proces_1
conditie_2
proces_2
conditie_3
proces_3
........
conditie_n
proces_n
-ALT creaza un proces complex care lanseaza in executie un singur proces
corespunzand primului canal de intrare care este gata de comunicatie:

ALT
canal_1 ? x
proces_1
canal_2 ? x
proces_2
canal_3 ? x
proces_3
...........
canal_n ? x
proces_n

Ordinea specificarii canalelor nu conteaza, selectia se face in functie de momentul de


timp cand canalul este gata de comunicatie.
-nu se pot defini variabile partajate intre doua procese (chiar daca procesele se
executa pe acelasi transputer).

=> Canalele de comunicatie Occam sunt sincrone si fara buffer:

(procesel A si B fac parte din componentele unei constructii PAR).


Aplicatie: calculul unei aproximatii a numarului π:

1
4
0 1  x 2 dx  4(arctg (1)  arctg (0))  

=> prin integrare numerica (inaltimea fiecarui dreptunghi este valoarea functiei in
mijlocul subintervalului respectiv).
Algoritmul secvential:

#define n . . . . .
sum = 0.0;
w = 1/n;
for (i=0; i<n; i++) {
x = (i+0.5)*w;
sum += 4/(1+x*x);
}
sum *=w;

=> Timpul de calcul este O(n).


Se considera o retea de p transputere inlantuite, primul transputer fiind conectat la
calculatorul gazda:

=>fiecarui transputer: n/p subintervale pentru suma ariilor dreptunghiurilor. Etape:

1)fiecare transputer calculeaza secvential si insumeaza n/p arii;

2)fiecare transputer primeste de la vecinul dreapta suma partiala, o insumeaza


cu propria valoare si o trimite la vecinul stanga, astfel incat se obtine rezultatul in nodul
0.
VAL INT p IS 8:
VAL INT n IS 1024:
[p] CHAN OF REAL chan: --vector de canale
INT s:
SEQ
s:=n/p
PAR i=0 FOR p-1 --vector de procese paralele
REAL x, partial_sum, w:

SEQ
partial_sum:=0.0
w:=1/n
x:=((i*s)+0.5)*w
SEQ j=0 FOR s-1
SEQ
partial_sum:=partial_sum+(4/(1+x*x))
x:=x+w
partial_sum=partial_sum*w
--transferul sumelor partiale si acumulare in procesorul 0
IF
i=p-1
chan[p-1] ! partial_sum
i<p-1
SEQ
REAL sum:
chan[i+1] ? sum
chan[i] ! sum+partial_sum
APLICATII PARALELE PE UN HIPERCUB

Algoritmi pe un hipercub

Structura hipercub -> implementarea unui numar mare de algoritmi necesitand


comunicatii all-to-all.

Sablon: un formular de program care poate fi completat cu informatii specifice


aplicatiei pentru implementarea unui algoritm paralel.

Hipercubul conecteaza fiecare din toate cele P taskuri (procesoare) la log2P alte taskuri.
-model SPMD;
-initial: o variabila de stare <- o data furnizata la intrare;
-log2P pasi;
-la fiecare pas fiecare task:
-schimba starea locala cu unul din vecini;
-combina mesajul primit de la vecin cu starea curenta -> starea urmatoare;
=> rezultatul: starea generata la pasul final.
procedure hypercube (myid, input, logp, output)
begin
state=input //starea locala init cu input
for i=0,logp-1 //repeta de logp ori
dest=myid XOR 2i //determina vecinul
send state -> dest //schimba datele
receive message <- dest
state=OP(state,message) //executa operatia
endfor
output=state //rezultatul final
end

Implementarea unui algoritm particular: prin definirea operatiei OP.


Reducere de vector

Algoritmul pecedent se poate utiliza pentru o reducere de vector utilizand orice


operator comutativ asociativ (ex: adunare, maxim, inmultire).

Reducerea a patru vectori de lungime N=4 distribuiti la patru taskuri => algoritmul in
log24=2 pasi. La fiecare pas fiecare task:
-executa schimbul a N date cu un vecin;
-realizeaza N operatii de combinare.
In cazul general de reducere vectoriala fiecare task dintre P taskuri:
-furnizeaza un vector de N valori;
-executa N operatii separate de reducere;
=> vector de N rezultate. Reducerea: in log2P pasi.

Timpul necesar:

Treducere = log2P (ts + N (tw + top))

unde
-top = timpul cerut de operatia de reducere;
-tw = timpul de comunicatie;
-ts = timpul de lansare (start-up).

Algoritmul este eficient pentru valori mici ale lui N (cand predomina timpul de lansare).
Varianta a algoritmului: algoritm recursiv cu injumatatire => reducerea semnificativa a
volumului mesajelor comunicate. Algoritmul aplicat de doua ori: in faza de reducere
fiecare procesor comunica si combina N/2 date in prima iteratie, apoi jumatate N/4 in a
doua, etc. => fiecare procesor comunica in total N(P-1)P date in log2P pasi. Se obtine
suma globala, iar vectorul rezultat de N componente este uniform distribuit la cele P
procese.
Timpul consumat:

P 1
Treducere recursiva  t s 2 log 2 P  (t w 2  top ) N
P

Solutia trimite de doua ori mai multe mesaje, dar cu mai putine date si face mai putine
calcule => mai eficient pentru anumte valori ale lui N si P, si pe anumite masini.
Broadcast

Similar: se poate defini un algoritm eficient de broadcast. N valori localizate in nodul


radacina sunt trasnmise la toate cele P-1 noduri, utilizand un arbore binar de
comunicatie.
Timpul necesar:

Tbroadcast = log2P (ts + twN)

Algoritmul: eficient pentru valori mici ale lui N si P.


Pentru valori mari ale lui N si P multe procesoare sunt inactive cea mai mare parte a
timpului si Tbroadcast este dominat de log2P twN.
=> mai eficient sa se sparga mesajul in componente mai mici si sa se routeze
componentele separat prin reteaua hipercub. Timpul necesar :

Tbroadcast = 2(ts log2P + twN)


Transpunerea de matrici.

Matricea A de transpus si transpusa A' sunt distribuite printre procese => executia
algoritmului implica comunicatii.

Se considera o descomunere pe coloane a celor doua matrici repartizate proceselor


=> comunicatii all-to-all.
Algoritmul: P-1 pasi. La fiecare pas fiecare task: interschimba N2/P2 date cu un alt task.

Timpul necesar:

N2
Ttranspunere simpla  t s ( P  1)  t w ( P  1) 2
P
N=P=8 => log2P pasi.. Fiecare proces are o singura coloana din A, iar in final fiecare
va avea o singura linie din A'.
La fiecare pas fiecare proces: schimba ½ din datele sale (reprezentate umbrit in desen).
In partea a doua a desenului: sursele componentelor detinute de procesul 0, la fiecare
pas al algoritmului.
Procesele sunt partitionate in doua seturi: taskurile corespunzatoare din cele doua seturi
interschimba ½ dintre datele lor: taskurile 0…p/2-1 comunica jumatatea inferioara a
datelor lor, iar taskurile P/2…P-1 comunica jumatatea superioara. Aceasta partitionare
si interschimb se repeta pana cand fiecare set contine un singur task.

Daca fiecare dintre cele log2P mesaje are dimensiunea N2/(2P) timpul necesar este:

N2
Ttranspunere hipercub  t s log 2 P  t w log 2 P
2P

Algoritmul hipercub transmite aprox. P/log2P mai putine mesaje, dar de (log2P)/2 ori
mai multe date.

Algoritmul hipercub: eficient in probleme mici cu timp de lansare mare si timp de


transfer mic!
Sortare
Algoritm de sortare "mergesort" pentru N valori (multiplu al numarului de procesoare
P=2d). Algoritmul secvential:
daca lungimea secventei de intrare <2 atunci return
partitioneaza secventa de intrare in doua jumatati
sorteaza cele doua subsecvente utilizand acelasi algoritm
fuzioneaza cele doua subsecvente sortate => secventa de iesire
Exemplu:

Timpul necesar: O(N log2N).

Solutia paralela: necesita doi algoritmi: "compare-exchange" si "parallel merge".


Compare-exchange: fuzioneaza doua secvente ordonate de lungime M,
continute in taskurile A si B.
-ambele taskuri contin M valori si toate elementele din A sunt ≤ elementele din B;
-fiecare task trimite datele sale la celalalt task;
-taskul A identifica M elemente cele mai mici, iar restul le descarca (necesitand intre
M/2 pana la M comparatii);
-taskul B identifica M elemente cele mai mari.
Exemplu: M=4.
Parallel Merge. Un algoritm “parallel merge" executa o operatie de fuzionare
peste doua secvente de lungime M2d, fiecare distribuita peste 2d taskuri pentru a
produce o secventa singulara sortata de lungime M2d+1 distribuita peste 2d+1 taskuri.
Aceasta se obtine prin utilizarea sablonului de comunicatie hipercub.

Exemplu: algoritmul paralel merge-sort in hipercuburi:

Intr-un hipercub de dimensiune d fiecare task executa d operatii de comparatie si


interschimb. Sagetile orientate de la taskul "high" catre taskul "low" in fiecare
interschimb.
Fiecare din cele 2d+1 taskuri se implica in d+1 pasi compara-interschimba, cate un pas
cu fiecare vecin. Fiecare nod executa algoritmul general, aplicand la fiecare pas
urmatorul operator:

if ( myid AND 2i > 0 ) then


state = compare_exchange_high(state,message)
else
state = compare_exchange_low(state,message)
endif

Operatorul logic AND este utilizat pentru a determina daca taskul este "high" sau "low"
intr-un interschimb particular, iar myid si i sunt ca in algoritmul general.
Mergesort. Algoritmul “parallel mergesort" (fiecare task):

procedure parallel_mergesort(myid, d, data, newdata)


begin
data = sequential_mergesort(data)
for dim = 1 to d
data = parallel_merge(myid, dim, data)
endfor
newdata = data
end

-fiecare task sorteaza secventa sa locala utilizand mergesort secvential;


-utilizand structura de comunicatie hipercub: fiecare dintre cele P=2d taskuri executa
algoritmul mergesort paralel de d ori pentru subcuburi de dimensiune 1..d;
-fuziunea paralela numarul i: ia doua secvente fiecare distribuita peste 2i-1 taskuri si
genereaza o secventa sortata distribuita peste 2i taskuri;
-dupa d astfel de fuziuni se obtine o singura lista distribuita peste 2d taskuri.
Analiza performantei. Numarul total de operatii comparatie-interschimb este:
d
d (d  1)

i 1
i
2
Fiecare operatie de comparatie-interschimb necesita un mesaj continand N/P date
=> timpul necesar de comunicatie per procesor:
d (d  1) N d (d  1)
Tcomunicatie  ts  tw
2 P 2
sunt cuprinse sortarea initiala din procesor (implica Nlog2(N/P) comparatii) si
comparatiile executate in timpul fazei de comunicatie (cel putin Nd(d+1)/2 comparatii)
=> timpul total pentru cele P procesoare:

 N d (d  1)   d (d  1) 
Tcomp  tc N  log 2    tc N  log 2 N  
 P 2   2 
Algoritmul perfect echilibrat
=> se presupune ca timpii inactivi sunt neglijabili. Astfel:

Tcomp N d (d  1)  d (d  1) N d (d  1)
T  Tcomunicatie  tc  log 2 N    ts  tw 
P P 2  2 P 2
 N N  (log 2 P) 2
  tc  ts  tw  daca (log 2 P) 2  log 2 N
 2P P 2
Programarea aplicatiilor paralele pe nCUBE-2

nCUBE-2 ( de la nCUBE Corporation) cu 8192 de noduri (procesor RISC, 32


biti, 64 MB memorie) conectate in retea hipercub.
Primitivele de transfer de mesaje in reteaua hipercub -> C si Fortran.

Transmisia (neblocanta) de mesaje:

int nwrite (char * buffer, int dim, int address, int type);

unde:
type: tipul de transfer de mesaje (sincron sau asincron);
buffer: adresa de memorie (in procesul care executa functia) de unde se
transmite mesajul;
dim: lungimea mesajului;
address: adresa de destinatie a mesajului (un nod, grup de noduri la
"multicast", toate nodurile la "broadcast").
Receptia (blocanta):

int nread (char * buffer, int dim, int source, int type);

Identificarea nodului curent (in care se desfasoara executia functiei):

void whoami (int * node, int * res1, int *res2, int *cube);
unde:
node: pointer la variabilele in care whoami depune numarul (eticheta) nodului;
cube: pointer la variabila in care se depune dimensiunea subcubului alocat
programului curent;
res1, 2: nu se utilizeaza.

Proiectarea algoritmului:
-se partitioneaza programul intr-un numar de procese;
-se distribuie aceste procese nodurilor sistemului.
Exemplu: calculul aproximatiei numarului π.

/*functia de transfer de mesaje in hipercub*/


void fan_in (float *value, int node, int d) {
int dest, i, source, type;
float temp;
type=FANIN;
for (i=d-1; i>=0; i--)
if (node<(1<<i)) { //00..00XX..X 0 in pozitia i
source=node^(1<<i);
nread (&temp, sizeof(float), &source, &type);
*value+=temp;
}
else
if (node<(1<<(i+1))) { //00..01XX..X
dest=node^(1<<i);
nwrite(value,sizeof(float),dest, type);
}
}
/*functia principala a programului*/
void main (int argc, char *argv[]) {
#define n 1024
int i;
int d; /*dimensiunea hipercubului*/
int p; /*numar de noduri*/
int node; /*eticheta nodului curent*/
int s; /*numar de subintervale/procesor*/
float x; /*mijloc dreptunghi pe axa x*/
float w=1/n; /*latimea intervalului*/
int res1, res2;
float sum;
whoami (&node, &res1, &res2, &d);
p=1<<d; /*2 la puterea d*/
s=n/p; /*se presupune n divizibil cu p*/
sum=0.0;
x=((node*s)+0.5)*w;
for (i=node*s; i<(node+1)*s; i++) {
sum+=4/(1+x*x);
x+=w;
}
fan_in (&sum, node, d);
if (node==0) /*rezultat nodul 0*/
printf ("Aproximatia numarului pi este %f\n", sum);
}
Etape:
-fiecare nod calculeaza un rezultat partial pentru n/p subintervale (n = numar
total de subintervale, p = numar de procesoare);
-etapa de comunicatie pentru calcularea sumei finale.

Fiecare nod:
-executa functia main (fiecare nod are o copie) -> calcule locale;
-apeleaza functia de transfer de mesaje fan_in.

p procesoare => subcub d-dimensional (apel functie whoami: p = 2d).

Rezultatul final (suma rezultatelor partiale in p noduri) => nodul 0 (00...0).


Comunicatia in hipercub:
Performante. Fiecare procesor executa n/p pasi pentru rezultatul partial, iar
comunicatia necesita log2p pasi:
-timpul de executie:

n
T p  O(  log 2 p)
p

-accelerarea:

 
 p 
S  O 
 1  p log 2 p 
 
 n 

-eficienta:
 
 1 
E  O 
 1  p log 2 p 
 
 n 
BIBLIOTECA MPI

-biblioteca de functii C sau subrutine Fortran => comunicatia intre procese;

Avantaje:
-portabilitatea codului sursa;
-implementari eficiente pe o varietate de platforme (inclusiv Unix si
Windows NT/XP);
-functionalitate (tipuri diferite de comunicatie, tipuri de date definite
de utilizator, topologii definite de utilizator);

Dezavantaje:
-mecanismul de lansare a unei aplicatii MPI dependent de platforma;
-nu permite gestiunea dinamica a proceselor (modificarea numarului
de procese in timpul rularii).
Forma generala a unui program MPI:

include fisiere header MPI


declaratii variabile
initializarea mediului MPI
executa operatii si transfer de mesaje
incheie mediu MPI

=> functiile MPI: rezultat intreg = cod succes / insucces;


daca cod  MPI_SUCCESS => eroare!
Comunicatori

-transferul de mesaje intre procese: in cadrul unor comunicatori.


-comunicator initial predefinit: MPI_COMM_WORLD (cuprinde toate procesele)

-se pot defini noi comunicatori;


-in cadrul unui comunicator fiecare proces are un identificator („rank”);
-un proces poate sa apartina la mai multi comunicatori in acelasi timp, cu
identificatori diferiti.
Functii pentru gestiunea mediului MPI

MPI_Init (&argc,&argv)

initializeaza mediul MPI, unde parametrii functiei reprezinta argumentele liniei de


comanda.

MPI_Comm_size (comm,&size)

furnizeaza pentru comunicatorul comm numarul de procese componente size.

MPI_Comm_rank (comm,&rank)

furnizeaza identificatorul procesului care a apelat-o (rank) in cadrul


comunicatorului (comm).

MPI_Finalize ()

incheie mediul de executie MPI.


Exemplu: utilizarea unor functii de gestiune a mediului MPI.

#include "mpi.h"
#include <stdio.h>
int main(int argc,char *argv)
{
int numtasks, rank, rc;
rc = MPI_Init(&argc,&argv);
if (rc != MPI_SUCCESS) {
printf ("Error starting MPI program. Terminating.\n");
MPI_Abort(MPI_COMM_WORLD, rc);
}
MPI_Comm_size(MPI_COMM_WORLD,&numtasks);
MPI_Comm_rank(MPI_COMM_WORLD,&rank);
printf ("Number of tasks= %d My rank= %d\n", numtasks,rank);
/******* do some work *******/
MPI_Finalize();
return 0;
}
Comunicatii punct-la-punct

=> comunicatie punct-la-punct: un proces transmite (executa “send”) si al


doilea receptioneaza (executa “receive”).
Functiile utilizate pentru transmisie sunt:

Functiile utilizate pentru receptie sunt:


Pentru comunicatia blocanta standard se utilizeaza la transmisie:

MPI_Send (&buf,count,datatype,dest,tag,comm)

care, transmite din zona buf, count date de tipul datatype la procesul destinatie dest,
din cadrul comunicatorului comm, mesajul avand identificatorul tag.

Pentru receptie:

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

datele fiind recptionate in zona buf la procesul care a apelat functia, in lungime
count de tipul datatype, de la procesul sursa source din cadrul comunicatorului comm,
mesajul avand identificatorul tag. Informatii de stare pentru mesajul receptionat sunt
depuse la status.
Exemplu: utilizarea functiilor de transmisie / receptie blocante.

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

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


int numtasks, rank, dest, source, rc, count, tag=1;
char inmsg, outmsg='x';
MPI_Status Stat;

MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD, &numtasks);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
if (rank == 0) {
dest = 1;
source = 1;
rc = MPI_Send(&outmsg, 1, MPI_CHAR, dest, tag, MPI_COMM_WORLD);
rc = MPI_Recv(&inmsg, 1, MPI_CHAR, source, tag, MPI_COMM_WORLD, &Stat);
}

else if (rank == 1) {
dest = 0;
source = 0;
rc = MPI_Recv(&inmsg, 1, MPI_CHAR, source, tag, MPI_COMM_WORLD, &Stat);
rc = MPI_Send(&outmsg, 1, MPI_CHAR, dest, tag, MPI_COMM_WORLD);
}

rc = MPI_Get_count(&Stat, MPI_CHAR, &count);


printf("Task %d: Received %d char(s) from task %d with tag %d \n",
rank, count, Stat.MPI_SOURCE, Stat.MPI_TAG);

MPI_Finalize();
return 0;
}
Transmisia si receptia neblocante:

-faza de initiere a operatiei;


-faza de asteptare pentru terminarea operatiei.

Initierea transmisiei:

MPI_Isend (&buf,count,datatype,dest,tag,comm,&request)

Initierea receptiei:

MPI_Irecv (&buf,count,datatype,source,tag,comm,&request)

(parametrii asemanatori cu functiile blocante).


Pentru incheierea unei operatii de transmisie sau receptie fara blocare:

MPI_Wait (&request,&status)
functie blocanta si

MPI_Test (&request,&flag,&status)
functie neblocanta pentru o singura cerere transmisie / receptie

MPI_Waitall (count,&array_of_requests,&array_of_statuses)
functie blocanta si

MPI_Testall (count,&array_of_requests,&flag,&array_of_statuses)

functie neblocanta pentru mai multe cereri.


Exemplu: comunicatie punct-la-punct neblocanta.

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

int main(int argc,char *argv) {


int numtasks, rank, next, prev, buf[2], tag1=1, tag2=2;
MPI_Request reqs[4];
MPI_Status stats[4];

MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD, &numtasks);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
prev = rank-1;
next = rank+1;
if (rank == 0) prev = numtasks - 1;
if (rank == (numtasks - 1)) next = 0;

MPI_Irecv(&buf[0], 1, MPI_INT, prev, tag1, MPI_COMM_WORLD, &reqs[0]);


MPI_Irecv(&buf[1], 1, MPI_INT, next, tag2, MPI_COMM_WORLD, &reqs[1]);

MPI_Isend(&rank, 1, MPI_INT, prev, tag2, MPI_COMM_WORLD, &reqs[2]);


MPI_Isend(&rank, 1, MPI_INT, next, tag1, MPI_COMM_WORLD, &reqs[3]);

{ executa secventa de operatii }

MPI_Waitall(4, reqs, stats);

MPI_Finalize();
return 0;
}
Comunicatii colective

-un numar mai mare de procese comunica intre ele in diferite moduri;

-avantaje in raport cu operatiile punct-la-punct:

-reducerea posibilitatii de eroare;


-reducerea numarului de linii de program;
-sursa este mai lizibila pentru depanare si modificare;
-viteza de executie creste.
Broadcast: un singur proces trimite cate o copie a unei date la toate celelalte
procese dintr-un grup.

MPI_Bcast (&buffer,count,datatype,root,comm)

Procesul root trimite blocul de date din buffer de lungime count si tip datatype la toate
procesele din cadrul comunicatorului, fiecare proces plasand datele in memoria sa in
zona buffer.
Scatter: un bloc de date (un tablou de un anumit tip) de la un proces este
impartit in bucati si distribuit uniform la diferite procese.

MPI_Scatter (&sendbuf,sendcnt,sendtype,&recvbuf,
recvcnt,recvtype,root,comm)

Procesul root trimite din zona sendbuf de lungime sendcnt si tip sendtype la fiecare
proces al comunicatorului comm cate un set de date depus in zona recvbuf, de lungime
recvcnt si tip recvtype.
Gather colecteaza blocuri de date de la un grup de procese si le reasambleaza
in ordinea corecta la un singur proces.

MPI_Gather (&sendbuf,sendcnt,sendtype,&recvbuf,
...... recvcount,recvtype,root,comm)

Procesul root receptioneaza in recvbuf de lungime recvcount si tip recvtype cate un bloc
de date de la fiecare proces al comunicatorului comm din zona sendbuf lungime sendcnt
si tip sendtype.
Reducere: un singur proces (procesul radacina) colecteaza datele de la celelalte
procese dintr-un grup si le combina pe baza unei operatii intr-o singura data.

MPI_Reduce (&sendbuf,&recvbuf,count,datatype,op,root,comm)

Datele de la toate procesele comunicatorului comm din zona sendbuf lungime count si
tip datatype sunt prelucrate cu operatia de reducere op si rezultatul inscris la procesul
root in zona recvbuf.
Operatia poate sa fie:
Sincronizare: prelucrarile pot continua numai daca toate procesele au ajuns
intr-un anumit punct al prelucrarilor.

MPI_Barrier (comm)

Blocheaza procesele comunicatorului comm pe masura ce functia este apelata de


catre acestea pana cand toate procesele comunicatorului au apelat aceasta functie. La
revenirea din functie toate procesele sunt sincronizate.
Exemplu: operatie „scatter” pentru liniile unei matrici.

#include "mpi.h"
#include <stdio.h>
#define SIZE 4

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


int numtasks, rank, sendcount, recvcount, source;
float sendbuf[SIZE][SIZE] = {
{1.0, 2.0, 3.0, 4.0},
{5.0, 6.0, 7.0, 8.0},
{9.0, 10.0, 11.0, 12.0},
{13.0, 14.0, 15.0, 16.0} };
float recvbuf[SIZE];
MPI_Init(&argc,&argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &numtasks);

if (numtasks == SIZE) {
source = 1;
sendcount = SIZE;
recvcount = SIZE;
MPI_Scatter(sendbuf,sendcount,MPI_FLOAT,recvbuf,recvcount,
MPI_FLOAT,source,MPI_COMM_WORLD);

printf("rank= %d Results: %f %f %f %f\n",rank,recvbuf[0],


recvbuf[1],recvbuf[2],recvbuf[3]);
}
else
printf("Must specify %d processors. Terminating.\n",SIZE);

MPI_Finalize();
return 0;
}
Biblioteca PVM
-functii portabile de nivel inalt pentru C si Fortran -> comunicatia in cadrul
unui grup de procese;
-compozitia grupului de procese este dinamica => cost suplimentar destul de
ridicat;
-un multicalculator (o retea de calculatoare) -> calculator virtual paralel, prin
transfer de mesaje;
-permite controlul proceselor, transmiterea si receptia mesajelor, sincronizarea
intre procese.

Masina virtuala paralela PVM :

-procesele server PVM (demonii PVM);

-biblioteca de functii PVM.


Initializarea masinii virtuale PVM: prin lansarea procesului server master cu functia:

pvm_start_pvmd

urmata de apelul functiei de setare a optiunilor de comunicatie:

pvm_setopt

Pe procesorul server master : hostfile (numele statiilor, caile de fisiere executabile, caile
pentru procesele server (demonii) din fiecare statie, contul utilizatorului, parola etc.).

Adaugare statii noi:

pvm_addhosts

Excludere statii:

pvm_delhosts
Oprirea masinii virtuale:
pvm_halt

Atasarea la masina virtuala PVM :


pvm_mytid => identificator de task (tid), numar intreg pe 32 de biti.

Crearea dinamica de procese:


pvm_spawn

Exemplu:
numt=pvm_spawn ("my_task", NULL, PvmTaskDefault, 0, n_task, tids);

creaza n_task procese care executa programul "my_task". Rezultatul functiei este
numarul efectiv de procese create (numt). Identificatorul fiecarui task creat este depus
intr-un element al vectorului tids.

Un proces poate parasi configuratia masinii PVM:


pvm_exit

sau poate fi terminat de alt proces:


pvm_halt (tid)
Exemplu: program "Hello World!":

main()
{
int cc, tid, msgtag;
char buf[100];

printf("i'm t%x\n", pvm_mytid());

cc = pvm_spawn("hello_other", (char**)0, 0, "", 1, &tid);

if (cc == 1) {
msgtag = 1;
pvm_recv(tid, msgtag);
pvm_upkstr(buf);
printf("from t%x: %s\n", tid, buf);
} else
printf("can't start hello_other\n");

pvm_exit();
}
Modele de programare ale interfetei PVM

-SPMD (Single Program Multiple Data): n instante ale aceluiasi program sunt
lansate ca n taskuri ale unei aplicatii paralele, folosind comanda spawn de la consola
PVM sau manual in cele n statii simultan. Initializarea mediului de programare:
specificarea statiilor pe care se executa cele n taskuri (nu se apeleaza pvm_spawn).

-MPMD (Multiple Program Multiple Data): unul sau mai multe taskuri sunt
lansate in diferite statii si acestea creaza dinamic alte taskuri.
Comunicatia punct-la-punct

Mesaj: proces A -> proces B => procesul A initializeaza bufferul de transmisie:

int pvm_initsend (int encode);

(encode stabileste tipul de codare a datelor in buffer) => identificator al bufferului de


transmisie in care se depun datele impachetate cu:

pvm_pack

Transmiterea mesajului:

int pvm_send (int tid, int msgtag);

(functie blocanta) tid = identificatorul mesajului de receptie, msgtag = tipul mesajului


(specifica prelucrari la receptie) => valoarea 0 pentru transmisie corecta si –1 la eroare.
Receptia:

int pvm_recv (int tid, int msgtag);

(functie blocanta) tid = identificatorul procesului sursa. Receptia: si pentru orice


transmitator (tid = -1) si orice tip de mesaj (msgtag = -1). => identificatorul bufferului
de receptie (bufid), de unde datele sunt extrase cu:

pvm_unpack

Receptia neblocanta:

pvm_nrecv
Comunicatia colectiva:

Atasarea unui proces la un grup (daca grupul nu exista, el este creat):


int pvm_joingroup (char *group_name);

=> 0 pentru procesul care creaza grupul si valoarea cea mai mica disponibila in grup
pentru fiecare proces urmator (un proces poate sa apartina la unul sau mai multe
grupuri).

Parasirea unui grup de catre un proces:


int pvm_lvgroup (char *group_name);

Obtinerea de informatii despre grup:


pvm_getinst => identificatorul instantei procesului in grup;

pvm_getid => identificatorul procesului;

pvm_gsize => dimensiunea grupului.


Comunicatiile colective (intre procesele membre ale unui grup) :

int pvm_bcast (char *group_name, int msgtag);

difuzeaza asincron mesajul cu flagul msgtag din bufferul de transmisie al procesului


apelant catre toate procesele membre ale grupului group_name (procesul apelant poate
sa fie sau nu membru al grupului).

int pvm_gather (void *g_arrray, void *myarray, int dim, int type, int msgtag,
char *group_name, int root);

colecteaza mesajele cu flagul msgtag, de la toate procesele grupului, in procesul


radacina (definit de utilizator) root, colectarea facandu-se in vectorul g_array, de
dimensiune dim si tip type. Fiecare proces trebuie sa apeleze pvm_gather.
int pvm_scatter (void *myarray, void *s_arrray, int dim, int type, int msgtag,
char *group_name, int root);

distribuie uniform tuturor proceselor din grup un vector de date de tipul type, cu numele
s_array si de dimensiune dim, aflat in spatiul de adresa al procesului radacina root.
Fiecare proces apeleaza pvm_scatter. Receptia se face in vectorul my_array.

int pvm_reduce (int operation, void *myvals, int dim, int type, int msgtag, char
*group_name, int root);

efectueaza operatia de reducere paralela intre toate procesele membre ale unui grup.
Argumentul operation defineste operatorul de reducere (PvmMin, PvmMax, PvmSum,
PvmProduct). Fiecare proces efectueaza operatia de reducere a datelor din vectorul
local de date myvals, de tipul type, de dimensiune dim, iar valoarea rezultata este
transferata procesului radacina root, care obtine valoarea de reducere finala.

int pvm_barrier (char *group_name, int ntasks);

sincronizeaza procesele membre ale unui grup (procesul este blocat pana cand un numar
ntasks procese din grupul group_name au apelat aceasta functie).
Exemplu: operatia de reducere paralela in masina virtuala PVM.

#include ........
#include "pvm3.h"
#define NTASKS 4
int main () {
int mytid, tids[NTASKS-1], groupid, sum, info;
/*crearea grupului de comunicatie*/
mytid=pvm_mytid();
groupid=pvm_joingroup("summax");
sum=groupid;
/*primul proces creaza celelalte NTASK-1 procese*/
if (groupid==0) {
info=pvm_spawn("summax",NULL,PvmTaskDefault," ", NTASKS-1,
tids);
printf("GroupId=%d spawned %d tasks\n", groupid, info);
}
/*bariera prin inghetare pana ce NTASKS procese s-au alaturat grupului*/
pvm_freezgroup("summax", NTASKS);
/*calculul sumei in grup*/
pvm_reduce (PvmSum, &sum, 1, PVM_INT, 1, "summax", 0);
/*procesul 0 tipareste rezultatul*/
if (groupid==0) {
printf("sum=%d\n", sum);
}
/*sincronizare pentru ca toate procesele sa execute operatia
inainte de parasirea grupului*/
pvm_barrier ("summex", NTASKS);
pvm_lvgroup ("summax");
pvm_exit();
return 0;
}

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