• Nem Talált Eredményt

Pont-pont kommunikáció, nem-blokkoló függvények

5. Az OpenMPI API

6.9. példa - statistics.c

5.3.2. Pont-pont kommunikáció, nem-blokkoló függvények

else if ( rank == 2 ) {

err= MPI_Recv((void*)numbers, N, MPI_FLOAT, 0, 0, MPI_COMM_WORLD, &status);

ERROR(err, "MPI_Recv");

statistics[0]= 0;

for ( i= 0; i < N; ++i ) statistics[0]+= numbers[i];

err= MPI_Send(statistics, 1, MPI_FLOAT, 0, 0, MPI_COMM_WORLD);

ERROR(err, "MPI_Send");

}

else if ( rank == 3 ) {

float tmp;

err= MPI_Recv((void*)numbers, N, MPI_FLOAT, 0, 0, MPI_COMM_WORLD, &status);

ERROR(err, "MPI_Recv");

statistics[0]= 0;

tmp= 0;

for ( i= 0; i < N; ++i ) {

statistics[0]+= numbers[i];

tmp+= numbers[i]*numbers[i];

}

tmp/= N;

statistics[0]/= N;

statistics[0]= sqrt(tmp - statistics[0]*statistics[0]);

err= MPI_Send(statistics, 1, MPI_FLOAT, 0, 0, MPI_COMM_WORLD);

ERROR(err, "MPI_Send");

}

err= MPI_Finalize();

ERROR(err, "MPI_Finalize");

return 0;

}

A program futásának kimenete:

user@home> mpirun -machinefile hostfile.home -np 4 ./statistics Adjon meg egy 10 szamot!

1 2 3 4 5 6 7 8 9 10 Minimum: 1.000000 Maximum: 10.000000 Atlag: 5.500000 Szoras: 2.872281

Az előző példaprogrammal összehasonlítva a különbség az, hogy itt most az egyes folyamatok különböző munkát végeznek, és a vezérlő folyamat is dolgozik az üzenetek küldésének és fogadásának koordinálása között.

Fontos megjegyezni, hogy a blokkoló kommunikációs függvényeket nagy gonddal kell használnunk: ahogy a Pthreads esetén a mutex-változók zárása során, itt is könnyen holtpontra juthatunk ha a folyamatokban olyan vezérlési utak valósulnak meg, hogy két folyamat blokkoló függvényekkel várakozik egymással nem kompatibilis kommunikációs műveletek elvégzésére.

5.3.2. Pont-pont kommunikáció, nem-blokkoló függvények

A nem-blokkoló kommunikációs függvények paraméterezése hasonló a blokkoló függvényekéhez, azzal a fő különbséggel, hogy minden esetben megjelenik a paraméterlista utolsó elemeként egy MPI_Request* típusú paraméter. A függvények által ezen keresztül beállított MPI_Request típusú objektumok használhatók a kommunikációs művelet befejeződésének ellenőrzésére.

Nem-blokkoló üzenetküldést az MPI_Isend függvény segítségével valósíthatunk meg, nem-blokkoló üzenet fogadást pedig az MPI_Irecv függvénnyel.

Specifikáció:

int MPI_Isend( void* buf, int count,

MPI_Datatype datatype, int dest,

int tag, MPI_Comm comm,

MPI_Request* request);

Paraméterek: buf - A küldendő üzenetet tartalmazó puffer mutatója.

count - Az átküldendő, datatype méretű, buf címen található adatelemek száma.

datatype - Az átküldendő adatok típusa, az atomi típusokhoz kapcsolódó lehetséges paraméterértékeket a 6.1. táblázatban foglaljuk össze.

dest - A comm kommunikátorban az üzenet címzettjéhez rendelt azonosító.

tag - Egy nemnegatív szám, amelyet címkeként az üzenethez rendelhetünk; a fogadó oldalon a megfelelő fogadó függvényben ugyanezen címkét kell

használnunk az elküldött üzenet fogadásához.

comm - A kommunikációhoz használandó kommunikátor.

request - Egy MPI_Request típusú változó mutatója.

A függvényhívás után ezen változón keresztül ellenőrizhetjük az üzenetküldés állapotát.

Visszatérési érték: Sikeres végrehajtás esetén MPI_SUCCESS= 0, ellenkező esetben hibakód.

Specifikáció:

int MPI_Irecv( void* buf, int count,

MPI_Datatype datatype, int source,

int tag, MPI_Comm comm,

MPI_Request* request);

Paraméterek: buf - Az üzenetet fogadó puffer mutatója.

count - A buf címen, count darab, datatype méretű adatelem tárolására alkalmas memóriaterület van lefoglalva.

datatype - Az érkező adatelemek típusa, az atomi típusokhoz kapcsolódó lehetséges paraméterértékeket a 6.1. táblázatban foglaljuk össze.

source - A comm kommunikátorban az üzenet küldőjéhez rendelt azonosító.

tag - Egy nemnegatív szám, csak azokat az üzeneteket fogadja a függvény, amelyek a source azonosítójú feladótól tag címkével érkeznek.

comm - A kommunikációhoz használandó

kommunikátor.

request - Egy MPI_Request típusú változó mutatója.

A függvényhívás után ezen változón keresztül ellenőrizhetjük az üzenetküldés állapotát.

Visszatérési érték: Sikeres végrehajtás esetén MPI_SUCCESS= 0, ellenkező esetben hibakód.

Ahogy arról korábban beszéltünk, az MPI_Isend és MPI_Irecv függvények nem blokkolják a folyamat futását, azaz nem várják meg, hogy küldéskor az üzenet bekerüljön egy rendszer-pufferbe vagy fogadáskor megérkezzen a fogadó pufferbe. A függvények csak jelzik a futtató rendszernek, hogy a folyamat előkészített egy küldésre szánt üzenetet, vagy készen áll egy üzenet fogadására. Ismét megjegyezzük, hogy ezen függvények használata esetén természetesen nem szabad módosítanunk az elküldendő üzenetet tartalmazó puffert illetve nem szabad felhasználnunk a fogadott üzenetet egészen addig, amíg meg nem győződtünk róla, hogy azt már biztonságosan megtehetjük, azaz az üzenet elküldése vagy fogadása befejeződött. A megközelítés előnye, hogy amikor egy folyamat üzenetet akar fogadni vagy küldeni, nem biztos, hogy a kommunikációban részt vevő másik folyamat is készen áll az üzenet küldésére vagy fogadására. Blokkoló függvények használata esetén a folyamat megáll és megvárja, hogy a kommunikációban résztvevő másik folyamat is jelezze a futtató rendszernek az üzenet küldésére, fogadására való igényt. Ezalatt az idő alatt azonban hasznos munka is végezhető, ha nem-blokkoló függvényeket használunk.

Megjegyezzük, hogy a különböző üzenetküldő és fogadó függvények tetszőleges kombinációban használhatók, azaz blokkoló függvény által küldött üzenetet fogadhatunk nem-blokkoló függvénnyel és nem-blokkoló függvény által küldött üzenetet fogadhatunk tetszőleges blokkoló függvénnyel.

A nem-blokkoló függvényekhez kapcsolódóan egy kérdés marad csak: honnan tudjuk meg, hogy az üzenet küldése vagy fogadása egy folyamat szempontjából befejeződött? Ezen célra használhatóak az MPI_Request típusú paraméterek, és a hozzájuk kapcsolódó függvények, amelyeket az alábbiakban tekintünk át.

Specifikáció:

int MPI_Test( MPI_Request* request, int* flag,

MPI_Status* status);

int MPI_Wait( MPI_Request* request, MPI_Status *status);

int MPI_Waitall( int count, MPI_Request array_of_requests[],

MPI_Status array_of_statuses[]);

Paraméterek: request - A nem-blokkoló kommunikációs függvény által visszaadott MPI_Request objektum mutatója.

flag - Egy egész típusú változó mutatója. A

függvényhívás után a címzett változó logikai értékként kezelendő: az kommunikáció befejeződése esetén logikai igaz, ellenkező esetben hamis.

status - MPI_Status típusú változó címe, amelybe a kommunikációhoz kapcsolódó további információk kerülnek. Ha figyelmen kívül szeretnénk hagyni a visszaadott MPI_Status objektumot, használjuk az MPI_STATUS_IGNORE konstanst.

count - Az array_of_requests tömb elemeinek száma.

array_of_requests - MPI_Request típusú objektumokat tartalmazó tömb.

array_of_statuses - MPI_Status típusú objektumokat tartalmazó tömb. Ha figyelmen kívül akarjuk hagyni a visszaadott MPI_Status

objektumokat, használjuk az MPI_STATUSES_IGNORE konstanst.

Visszatérési érték: Sikeres végrehajtás esetén MPI_SUCCESS= 0, ellenkező esetben hibakód.

Az MPI_Test függvénnyel megvizsgálhatjuk, hogy befejeződött-e egy nem-blokkoló kommunikációs művelet.

Visszatérési értéke a második paraméterében jelenik meg logikai értékként. Ha a flag mutató által címzett egész érték logikailag igaz a függvény befejeződése után, akkor a kommunikációs művelet befejeződött.

Az MPI_Wait függvény, paraméterként kapja egy nem-blokkoló üzenetküldő vagy fogadó művelethez kapcsolódó MPI_Request változó címét és blokkolja a folyamat futását, amíg a kommunikációs művelet be nem fejeződik. Ha több kommunikációs művelet befejeződéséig szeretnénk blokkolni a program futását, az MPI_Waitall függvényt használhatjuk.

Az MPI_Isend variánsa az MPI_Ssend függvényhez hasonló koncepciót megvalósító MPI_Issend függvény, amely használata esetén az MPI_Test függvény akkor tér vissza logikailag igaz értékkel a flag címen és hasonlóan, az MPI_Wait és MPI_Waitall akkor tér vissza, ha a fogadó szál megkezdte az üzenet fogadását. A különbség tehát az, hogy MPI_Isend esetén akkor is visszatérnek a nevezett függvények, ha az üzenet egy átmeneti rendszer-pufferbe került, azaz az elküldött üzenetet tartalmazó puffer szabadon módosítható, míg az MPI_Issend használata esetén az üzenet fogadásának megkezdéséről győződhetünk meg az MPI_Test, MPI_Wait és MPI_Waitall függvényekkel.

További gyakran használt függvény még az MPI_Iprobe, amellyel azt ellenőrizhetjük, hogy van-e olyan üzenet, amely adott source küldő azonosítóval és tag címkével fogadásra vár.

Specifikáció:

int MPI_Iprobe( int source, int tag, MPI_Comm comm, int* flag,

MPI_Status* status);

Paraméterek: source - A comm kommunikátorban a küldő folyamat azonosítója.

tag - Az üzenet címkéje.

comm - A kommunikátor objektum.

flag - Egy egész típusú változó címe, amelybe az eredmény, mint logikai érték kerül.

status - MPI_Status típusú változó címe, amelybe további információk kerülnek az üzenetküldésről.

Visszatérési érték: Sikeres végrehajtás esetén MPI_SUCCESS= 0, ellenkező esetben hibakód.

Az MPI_Iprobe függvény esetén is használhatóak a korábban említett wildcard konstansok, amelyek használata esetén tetszőleges forrástól (MPI_ANY_SOURCE) érkező tetszőleges címkéjű (MPI_ANY_TAG) üzenetek jelenlétét kérdezhetjük le.

A következő példaprogram a korábban megírt, leíró statisztikákat kiszámító program nem-blokkoló függvényekkel megvalósított változatát mutatjuk be.

6.10. példa - statistics.c

#include <stdio.h>

#include <mpi.h>

#include <float.h>

#include <error.h>

#include <math.h>

#define N 10

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

err= MPI_Init(&argc, &argv);

ERROR(err, "MPI_Init");

err= MPI_Comm_rank(MPI_COMM_WORLD, &rank);

ERROR(err, "MPI_Comm_rank");

err= MPI_Isend((void*)numbers, N, MPI_FLOAT, i, 0, MPI_COMM_WORLD, requests + i - 1);

err= MPI_Waitall(6, requests, MPI_STATUSES_IGNORE);

ERROR(err, "MPI_Waitall");

printf("Minimum: %f\nMaximum: %f\nAtlag: %f\nSzoras: %f\n", statistics[0], statistics[1], statistics[2], statistics[3]);

}

else if ( rank == 1 ) {

err= MPI_Irecv((void*)numbers, N, MPI_FLOAT, 0, 0, MPI_COMM_WORLD, requests);

ERROR(err, "MPI_Recv");

err= MPI_Wait(requests, MPI_STATUS_IGNORE);

ERROR(err, "MPI_Wait");

statistics[0]= -FLT_MAX;

for ( i= 0; i < N; ++i )

if ( numbers[i] > statistics[0] ) statistics[0]= numbers[i];

err= MPI_Isend(statistics, 1, MPI_FLOAT, 0, 0, MPI_COMM_WORLD, requests);

ERROR(err, "MPI_Send");

}

else if ( rank == 2 ) {

err= MPI_Irecv((void*)numbers, N, MPI_FLOAT, 0, 0, MPI_COMM_WORLD, requests);

ERROR(err, "MPI_Recv");

err= MPI_Wait(requests, MPI_STATUS_IGNORE);

ERROR(err, "MPI_Wait");

statistics[0]= 0;

for ( i= 0; i < N; ++i ) statistics[0]+= numbers[i];

statistics[0]/= N;

err= MPI_Isend(statistics, 1, MPI_FLOAT, 0, 0, MPI_COMM_WORLD, requests);

ERROR(err, "MPI_Send");

}

else if ( rank == 3 )

{

float tmp;

err= MPI_Irecv((void*)numbers, N, MPI_FLOAT, 0, 0, MPI_COMM_WORLD, requests);

ERROR(err, "MPI_Recv");

err= MPI_Wait(requests, MPI_STATUS_IGNORE);

ERROR(err, "MPI_Wait");

statistics[0]= 0;

tmp= 0;

for ( i= 0; i < N; ++i ) {

statistics[0]+= numbers[i];

tmp+= numbers[i]*numbers[i];

}

tmp/= N;

statistics[0]/= N;

statistics[0]= sqrt(tmp - statistics[0]*statistics[0]);

err= MPI_Isend(statistics, 1, MPI_FLOAT, 0, 0, MPI_COMM_WORLD, requests);

ERROR(err, "MPI_Send");

}

if ( rank != 0 )

MPI_Wait(requests, MPI_STATUS_IGNORE);

err= MPI_Finalize();

ERROR(err, "MPI_Finalize");

return 0;

}

user@home> mpirun -machinefile hostfile.home -np 4 statistics Adjon meg 10 szamot!

2 10 4 3 5 7 8 1 9 6 Minimum: 1.000000 Maximum: 10.000000 Atlag: 5.500000 Szoras: 2.872281

Ahogy az a kimenetből látható, programunk a korábbival egyező eredményt ad, helyesen működik.

Nyilvánvaló, hogy a nem-blokkoló függvények használata esetén hosszabb kódot kell írnunk, mert a kommunikáción kívül a kommunikációs művelet befejezését is ellenőriznünk kell. A példában a 0 azonosítójú vezérlő folyamat küldi szét a beolvasott számokat a dolgozó folyamatoknak. Mivel azonban a vezérlő folyamatnak nem kell megvárnia a tömb megérkezését, ezért a nem-blokkoló küldő és fogadó függvények hívása után rögtön hozzáláthat a tömb legkisebb elemének meghatározásához. A kommunikációs műveletek befejeződését csak a vezérlő folyamat utolsó művelete, az eredmények kimenetre írása előtt kell biztosítanunk, erre az MPI_Waitall függvényt használjuk. A dolgozó szálak szerkezete hasonló: első lépésként meg kell kapniuk a felhasználó által megadott számokat tartalmazó tömböt. Annak megérkezése után végrehajtják a dedikált számítási feladatot, majd visszaküldik az eredményt a vezérlő folyamatnak. Azt, hogy a dolgozó folyamatok ne fejeződjenek be az eredmények biztonságos visszaküldése előtt, a program végén található MPI_Wait függvényhívással biztosítjuk. Figyeljük meg a példaprogramban, hogy ugyanazon változókat a különböző folyamatok különböző célokra használják!