Mpi curs introductiv Introducere



Yüklə 53,18 Kb.
tarix12.01.2019
ölçüsü53,18 Kb.
#94944

MPI

Curs introductiv
Introducere

Conceptul de paralelism presupune in general impartirea unui task (job) in sub-taskuri de dimensiuni mai mici ce se pot executa in paralel. Exista o serie de aplicatii mai mult sau mai putin complexe ce pot fi rezolvate in sisteme paralele. Intre domeniile in care se foloseste masiv calculul paralel se numara prelucrarea de imagini, meteorologia, fizica nucleara, modelare si simulare, etc. Exista in principiu doua tipuri de sisteme paralele si anume strans cuplate (sisteme multiprocesor) si slab cuplate (sisteme multicalculator sau sisteme distribuite) Exista doua paradigme de programare in cadrul sistemelor paralele: memorie partajata (shared memory) si comunicatia prin mesaje (Message Passing). Acestea se regasesc atat in cadrul sistemelor strans cuplate cat si in cadrul celor slab cuplate. System V IPC este un exemplu ce include ambele paradigme pentru sisteme strans cuplate, in timp ce pentru sisteme distribuite se pot mentiona OpenMP (Distributed Shared Memory) si MPI (Message Passing Interface) sau PVM (Parallel Virtual Machine). Trebuie remarcat faptul ca MPI nu se refera in mod exclusiv la sistemele slab cuplate.


Scurt istoric

A aparut in aprilie 1992 in cadrul conferintei Super Computing, versiunea finala aparand in 1994. In 1993, a fost publicat primul MPI Standard. Cea mai recenta versiune este MPI-2, publicata in iulie 1997. Exista o serie de implementari proprietare, insa cea mai cunoscuta disponibila domeniului public fiind MPICH.

In ceea ce priveste granularitatea, unitatea fundametala de calcul este procesul. Fiecare proces are un fir de executie (thread) independent pentru control si un spatiu de adrese propriu. Nu exista mecanisme pentru asignarea proceselor pentru anumite procesoare si nici pentru creerea/distrugerea proceselor. Exista insa posibilitatea crearii/distrugerii dinamice de grupuri de procese. Apartenenta proceselor la grupuri este statica, iar grupurile pot sa nu fie disjuncte. Nu exista suport explicit pentru multithreading, insa MPI-ul este conceput sa fie thread-safe.

Structura de baza a unui program MPI
Desi exista peste 125 de rutine MPI definite 6 dintre ele sunt cele mai folosite.

MPI_INIT - Initializeaza mediul MPI. Este PRIMA rutina ce trebuie apelata in orice program MPI. Exista o singura exceptie si anume pentru a verifica daca a fost sau nu intializat mediul MPI_Initialized()
MPI_FINALIZE – trebuie apelata de fiecare proces in parte in momentul in care isi termina executia. Dupa apelarea acestei rutine nici o alta procedura MPI nu mai poate fi apelata.
MPI_COMM_SIZE – intoarce numarul de procese
MPI_COMM_RANK – intoarce rangul procesului curent in cadrul grupului.
MPI_SEND – trimite mesaj
MPI_RECV – primeste mesaj

Hello World in MPI
Iata in continuare cel mai simplu program in MPI:
#include "mpi.h"

#include


int main( argc, argv )

int argc;

char **argv;

{

MPI_Init( &argc, &argv );



printf( "Hello world\n" );

MPI_Finalize();

return 0;

}
Presupunand ca programul de mai sus se afla in hello.c compilarea se face in felul urmator:

$ mpicc hello.c -o hello
iar un exemplu de rulare ar putea fi urmatorul

$ mpirun -np 4 hello


O varianta “imbunatatita”, care in care afisam si numarul de procese si rangul procesului curent, ar fi urmatoarea ( fisierul hello1.c ) :

#include "mpi.h"

#include

int main( argc, argv )

int argc;

char **argv;

{

int namelen, myid, numprocs;



char processor_name[MPI_MAX_PROCESSOR_NAME];
MPI_Init( &argc, &argv );

MPI_Comm_size(MPI_COMM_WORLD,&numprocs);

MPI_Comm_rank(MPI_COMM_WORLD,&myid);

MPI_Get_processor_name(processor_name,&namelen);


printf( "Process %d / %d on %s says: Hello world\n", myid, numprocs, processor_name );
MPI_Finalize();

return 0;

}
Compilare

$ mpicc hello1.c -o hello1


Daca rulam pe doua masini diferite (in cazul de fata ml si aiwa) outputul este in forma urmatoare:
a) pentru 4 procese:
[...@ml examples]$ mpirun -np 4 hello1

Process 0 / 4 on ml says: Hello world

Process 2 / 4 on ml says: Hello world

Process 1 / 4 on aiwa says: Hello world

Process 3 / 4 on aiwa says: Hello world
b) pentru 7 procese:
[...@ml examples]$ mpirun -np 7 hello1

Process 0 / 7 on ml says: Hello world

Process 4 / 7 on ml says: Hello world

Process 2 / 7 on ml says: Hello world

Process 1 / 7 on aiwa says: Hello world

Process 6 / 7 on ml says: Hello world

Process 5 / 7 on aiwa says: Hello world

Process 3 / 7 on aiwa says: Hello world



Grupuri, contexte si comunicatori
Grupurile, contextele si comunicatorii sunt folosite in MPI, in principal, pentru a face posibila folosirea de catre aplicatii a bibliotecilor. Comunicatorii sunt containere care contin mesaje si grupuri de procese alaturi de alte informatii aditionale(meta-data). Dupa initializare (MPI_INIT) exista un singur grup de procese in cadrul comunicatorului MPI_COMM_WORLD. Acesta este comunicatorul implicit (default) la initializarea mediului. Grupurile create ulteriror initializarii sunt subseturi ale grupului initial au la randul lor comunicatori asociati. Comunicatorii sunt cei care permit, acolo unde este nevoie, o impartire din punct de vedere logic a unei aplicatii MPI, impiedicand transferarea mesajelor intre biblioteci si/sau aplicatii ce fac parte din grupuri diferite. Exista doua tipuri de comunicatori, si anume: intra-comunicatori pentru comunicatia in cadrul unui aceluiasi grup de procese si inter-comunicatorii pentru comunicatia intre grupuri de procese. Mai multe detalii despre aceste notiuni vor fi prezentate intr-unul din cursurile urmatoare.





Paradigma "message passing"
“Message Passing” reprezinta transferul unor date ( mesaje ) intre doua procese. Presupune existenta:

    • unui expeditor/transmitator (sender), de o parte, si a unui destinatar/receptor (receiver), de cealalta parte. Comunicatia este initiata de catre expeditor.

    • unui mod de descriere a datelor

    • unui mod de identificare a proceselor

    • unui mod de identificare a mesajelor

Datele trimise (partea “utila” din mesaj) intre cele doua procese sunt descrise de un triplet de forma:



    • adresa unde sunt stocate datele (in C un pointer catre respectiva zona de memorie )

    • numar – cate astfel de elemente se vor regasi in mesaj

    • tipul de date

    • de baza (intreg, real, etc)

    • derivat (nu neaparat zone continue de memorie)

Procesele sunt identificate de perechea (comunicator, rang). Rangul este o valoare relativa a identificatorului de proces in cadrul grupului asociat comunicatorului respectiv.

Mesajele sunt identificate de o eticheta (tag).
MPI_SEND(buf, count, datatype, dest, tag, comm)
buf – adresa de la care se iau datele

count – numarul de elemente ce se vor regasi in mesaj

datatypetipul datelor din mesaj

dest – rangul procesului destinatie

tag – eticheta mesajului

comm – comunicatorul in cadrul caruia se realizeaza transferul


In momentul in care se iese din apelul acestei proceduri mesajul a fost copiat din buf, si acesta poate fi refolosit. Acest lucru presupune folosirea de buffere sistem. Daca acestea sunt insuficiente aceasta procedura se va bloca pana cand se va apela MPI_RECV la destinatar.
MPI_RECV(buf, count, datatype, source, tag, comm, status)
buf – adresa bufferului la care se scriu datele primite

count – numarul maxim de elemente ce se vor fi primite

datatype – tipul datelor din mesaj

dest – rangul procesului sursa (se poate folosi pe post de “wildcard” MPI_ANY_SOURCE)

tag – eticheta mesajului (se poate folosi pe post de “wildcard” MPI_ANY_TAG)

comm – comunicatorul in cadrul caruia se realizeaza transferul

status – informatii de stare a datelor din mesaj
In momentul in care se iese din aceasta metoda datele au fost copiate in buf. In cazul in care count este mai mare decat cel trimis de catre sender apelul se va intoarce cu eroare.
Exemplu:
#include

#include

#include
#define MESSAGE " Hello World! "

#define MSG_LENGTH (sizeof(MESSAGE))


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

{

int i, tag=1, tasks, iam, namelen;



char message[MSG_LENGTH];

MPI_Status status;

char processor_name[MPI_MAX_PROCESSOR_NAME];

MPI_Init(&argc, &argv);

MPI_Comm_size(MPI_COMM_WORLD, &tasks);

MPI_Comm_rank(MPI_COMM_WORLD, &iam);

MPI_Get_processor_name (processor_name, &namelen);
if (iam == 0) {

/* Root node sends message to everyone. */

strcpy(message, MESSAGE);

for (i=1; i

MPI_Send(message, MSG_LENGTH, MPI_CHAR, i, tag,

MPI_COMM_WORLD);

} else {

/* Receive message from root. */

MPI_Recv(message, MSG_LENGTH, MPI_CHAR, 0, tag,

MPI_COMM_WORLD, &status);

}

printf("Process %d / %d on %s got %s\n", iam, tasks, processor_name, message);



MPI_Finalize();

return 0;

}
$ mpicc hello_sndrcv.c -o hello_sndrcv
[...@ml examples]$ mpirun -np 4 hello

Process 0 / 4 on ml got Hello World!

Process 2 / 4 on ml got Hello World!

Process 1 / 4 on aiwa got Hello World!

Process 3 / 4 on aiwa got Hello World!

Iesirea de mai sus a fost obtinuta prin rularea pe aceleasi 2 calculatoare ca si la exemplul HelloWorld prezentat anterior.


MPI deadlocks

Comunicatia prin MPI poate conduce foarte usor la Deadlock-uri daca se folosesc scrieri/citiri sincrone. Un exmplu de astfel de deadlock poate fi urmatorul scenariu. Presupunem ca avem doua procese in cadrul carora comunicatia se face dupa urmatorul protocol


Primul proces trimite date catre cel de-al doilea si asteapta raspunsuri de la acesta. Cel de-al doilea proces trimite date catre primul si apoi asteapta raspunsul de la acesta. Daca bufferele sistem nu sunt suficiente se ajunge la deadlock. Orice comunicatie care se bazeaza pe bufferele sistem este nesigura din punct de vedere al deadlock-ului. In orice tip de comunicatie care include cicluri pot apare deadlock-uri.




Moduri de comunicare P2P


  1. Standard




    • Blocant

Folosind MPI_SEND si MPI_RECV prezentate anteriror. Se foloseste de obicei cand nu se poate continua decat pe baza rezultatului intors. Este predispus la deadlock-uri.


    • Non-blocant


MPI_ISEND( buf, count, datatype, dest, tag, comm, request )
Aceeasi sintaxa ca si la MPI_SEND singura diferenta fiind faptul ca se intoarce si un request handle. Apelarea acestei functii nu este blocanta. Pentru a testa daca trimiterea s-a facut cu succes se poate apela MPI_TEST sau MPI_WAIT, functii ce vor fi discutate in cele ce urmeaza.
MPI_IRECV( buf, count, datatype, source, tag, comm, request )
Aceeasi sintaxa ca si in cazul lui MPI_RECV cu mentiunea ca apelul acestei functii este nonblocant si in plus apare un request handle. Datele din buf nu pot fi accesate pana cand operatia de primire nu a fost terminata. Acest lucru se poate verifica/astepta fie cu MPI_TEST fie cu MPI_WAIT.
MPI_WAIT( request, status )
request este handle-ul intors din functiile nonblocante scriere (MPI_ISEND) sau citire (MPI_IRECV)

status – contine sursa ( rang-ul procesului sender ), eticheta ( tag-ul ) mesajului si codul de eroare


Din aceasta functie nu se revine pana cand operatia nonblocanta nu se termina. Dupa ce se iese din aceasta functie handle-ul din request este eliberat. In cazul lui MPI_ISEND in status NU se regasesc informatii referitoare la procesul receiver.
MPI_TEST( request, flag, status )
request – este handle-ul intors in una din cele doua operatii nonblocante de send sau receive.

flag – daca este true la iesirea din functie inseamna ca operatia a fost incheiata si in status se regasesc aceleasi informatii ca la iesirea din MPI_WAIT. Tot in acest caz la iesirea din functie se elibereaza si handle-ul din request.

La fel ca si in czul lui MPI_WAIT daca request se refera la o operatie de tipul MPI_ISEND in status nu se vor regasi informatii referitoare la procesul receiver.
Non-standard

Urmatoarele moduri de comunicatie sunt folosite doar de catre sender!



  • buffered

Aplicatia asigura buffer-ele necesare pentru trimiterea de mesaje. NU se mai folosesc buffere sistem.

Acest mod este predispus la erori de tip buffer overflow.


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

MPI_IBSEND( buf, count, datatype, dest, tag, comm, request )
Aceste functii sunt aceeasi functionalitatea ca cele discutatea anteriror cu singura deosebire ca de aceasta data nu se mai folosesc buffere sistem iar buf este specificat de catre user.

MPI_BUFFER_ATTACH( buf, size )

MPI_BUFFER_DETACH( buf, size )
Exemple:


  • Segmentation fault – attach pe buffer nealocat

#define BUFF_SIZE 1000


char *buf;

char buf1[500], buf2[500];


MPI_Buffer_attach(buff, BUFF_SIZE);


  • Buffer Overflow – folosirea unui buffer mai mic decat este necasar pentru send in MPI

#define BUFF_SIZE 1000


char *buf;

char buf1[500], buf2[500];


buf = (char*)malloc(BUFF_SIZE);

MPI_Buffer_attach(buff, BUFF_SIZE);

MPI_Bsend(buf1, 500, MPI_CHAR, 1, 1, MPI_COMM_WORLD);

MPI_Bsend(buf2, 500, MPI_CHAR, 2, 1, MPI_COMM_WORLD);




  • Corect

int size;


char *buf;

char buf1[500], buf2[500];


MPI_Pack_size(500, MPI_CHAR, MPI_COMM_WORLD, &size);

size += MPI_BSEND_OVERHEAD;

size *= 2;
buf = (char*)malloc(size);

MPI_Buffer_attach(buf, size);

MPI_Bsend(buf1, 500, MPI_CHAR, 1, 1, MPI_COMM_WORLD);

MPI_Bsend(buf2, 500, MPI_CHAR, 2, 1, MPI_COMM_WORLD);


MPI_Buffer_detach(buf, &size);



  • sincron


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

MPI_ISSEND( buf, count, datatype, dest, tag, comm, request )
Nu se termina pana ce nu se primeste confirmare ca receiver-ul a inceput sa primeasca mesanul. Confirmarea primita nu garanteaza ca receiver-ul a terminat de primit. Se poate folosi in loc de a trimite si primi confirmari.


  • ready


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

MPI_IRSEND( buf, count, datatype, dest, tag, comm, request )
Poate fi folosit doar daca receiver-ul a inceput deja sa astepte mesajul inainte ca sender-ul sa inceapa trimiterea mesajului. Se elimina partea de handshake intre parti, insa in cazul in care receiver-ul nu a inceput sa faca primeasca inainte ca sender-ul sa foloseasca MPI_RSEND rezultatul operatiei este nedefinit


Probleme legate de performanta
De obicei cea mai folosita varianta este cea cu trimitere/primire nonblocanta. Avantajele ar fi ca se pot suprapune calculele cu transmiterea si faptul ca acest mod este mult pai putin supus probabilitatii aparitiei unui deadlock.
Modul standard este in general suficient insa in cazul modului buffered pot aparea avantaje in cazul mesajelor mari, sau daca user-ul nu este sigur de cat de mari sunt bufferele oferite de sistem.
Modul sincron poate fi folosit foarte usor pentru a nu mai implementa, acolo unde este nevoie, ack pentru mesajele primite.
Daca insa nu sunt suprarpuneri de trimis/primit mesaje intre parti modul nonblocant are un overhead ceva mai mare decat modul blocant.
Yüklə 53,18 Kb.

Dostları ilə paylaş:




Verilənlər bazası müəlliflik hüququ ilə müdafiə olunur ©muhaz.org 2024
rəhbərliyinə müraciət

gir | qeydiyyatdan keç
    Ana səhifə


yükləyin