Hesham El-Rewini & Ted G. Lewis: Distributed and Parallel
Computing
| - výťah z kapitoly. |
opravené chyby: 123
4 |
Chapter 10: Message-Passing Interface (MPI)
- MPI application
- Task groups
- Communicators
- Virtual topologies
- Task communication
- Synchronization
- Collective operations
- Supervisor/worker application skeleton
MPI
- je štandardom na písanie message-passing programov
- vyvinutý 1993 - 1994 medzinárodnou skupinou nazývanou "MPI Fórum"
- cieľom je poskytnúť štandardnú knižnicu pre prenositeľné a efektívne
message-passing programy
- nie je jazyk, je štandard a má viac implementácií,
napr. MPICH (chameleon), LAM (local area multicomputer), ...
- pre C-jazyk aj FORTRAN
- vyvýja sa nový štandard MPI-2 (dynamické procesy...) (1995-...)
MPI aplikácia
Skupiny úloh
- úlohy v MPI môžu patriť do pomenovaných skupín (named groups)
- skupina v MPI je objekt typu MPI_Group a poskytuje kontext
- členovia skupiny majú pridelené jedinečné identifikátory (čísla, ranks)
- skupina je zoradenou množinou rankov, ktorá je spojitá a začína 0
- v MPI vždy 'existuje' aspoň jedna skupina (napr. príslušná k
MPI_COMM_WORLD)
- nová skupina sa dá vytvoriť:
- exlúziou:
MPI_Group_excl(existing_group, size, rank_array, &new_group)
MPI_Group_range_excl(existing_group, n, ranges, &new_group)
- inklúziou:
MPI_Group_incl(existing_group, size, rank_array, &new_group)
MPI_Group_range_incl(existing_group, n, ranges, &new_group)
ranges ... je pole trojíc rozmeru n, kde trojica je [od, do, obkrok]
- množinovými operáciami:
Komunikátory
- v každom message-passing systéme musí byť garantovaný bezpečný
komunikačný priestor, ktorý oddeľuje nesúvisiace správy od seba navzájom
(knižničné komunikácie od aplikačných-user)
- v PVM túto funkciu zabezpečuje démon proces
- v MPI by nepostačoval iba 'message tag', preto je tu zavedený koncept
komunikátora: je to väzba komunikačného kontextu na skupinu (group)
- komunikátory sú typu MPI_Comm
a delia sa na:
- intrakomunikátory (komunikácie vo vnútri jednej skupiny úloh)
- interkomunikátory (komunikácie medzi rozličnými skupinami úloh)
V ďalšom sa zameriame len na interné.
- default - po štarte aplikácie sú všetky úlohy asociované s komunikátorom
MPI_COMM_WORLD a jeho hodnota je
konštantná po celý čas behu aplikácie
- default existuje tiež MPI_COMM_SELF ktorý
zahŕňa iba úlohu samotnú
- rank úlohy (ako v skupine-group je typu integer) sa dá zistiť:
MPI_Comm_rank(communicator, &rank)
- skupina prislúchajúca danému komunikátoru sa dá zistiť:
MPI_Comm_group(communicator, &correspoding
group)
takto sa 'vygeneruje' prvá skupina (skupina sa nedá vytvoriť 'z ničoho')
- veľkosť prislúchajúcej skupiny:
MPI_Comm_size(communicator, &number_of_tasks)
- keď je potrebný nový kontext (nový komunikátor), program vykoná
synchronizované volanie jednej z nasledovných 3 funkcií, teda MUSIA
ho vykonať VŠETKY úlohy ktoré prislúchajú danému komunikátoru, aj keby
ten novo vzniknutý komunikátor neplánovali použiť !
- MPI_Comm_dup(oldcomm, &newcomm)
- MPI_Comm_create(oldcomm, group, &newcomm)
...group musi byť podmnožinou vzhľadom k oldcomm
- MPI_Comm_split(oldcomm, split_key, rank_key, &newcomm)
- ak split_key == MPI_UNDEFINED tak
sa vráti newcomm=MPI_COMM_NULL
- split_key a rank_key sú typu integer
- split_key 'pomenúva' novú skupinu (koľko bude rôznych hodnôt split_key
vo volaniach funkcie vo všetkých úlohách, toľko rôznych disjunkčných
skupín vznikne)
- rank_key určuje rank v novej skupine (iba zoradenie, pretože nemôže byť
nesúvislá)
Example 4:
MPI aplikácia má 5 úloh: T0, T1, T2, T3 a T4, ktoré majú rank
0, 1, 2, 3, 4 a 5. Nech všetky zavolajú funkciu MPI_Comm_split():
T0: MPI_Comm_split(MPI_COMM_WORLD, 8, 0, &newcomm)
T1: MPI_Comm_split(MPI_COMM_WORLD, 5, 1, &newcomm)
T2: MPI_Comm_split(MPI_COMM_WORLD, 8, 2, &newcomm)
T3: MPI_Comm_split(MPI_COMM_WORLD, 5, 3, &newcomm)
T4: MPI_Comm_split(MPI_COMM_WORLD, MPI_UNDEFINED, 4, &newcomm)
Potom skupina asociovaná s komunikátorom newcomm bude pozostávať
z úloh:
{T0,T2} v úlohách T0 a T2
{T1,T3} v úlohách T1 a T3
a v úlohe T4 bude prázdna.
Rank, ktorý budú mať úlohy v tejto novej skupine bude odvodený z ranku v
pôvodnej skupine (teda 0 a 1 v poradí, ako je uvedené).
Virtuálne topológie
- nie vždy je výhodné použiť lineárny ranking, napr. 2D, 3D pole, mriežka
- ku komunikátoru môžeme pridať atribút topológie (je 'nesený'
komunikátorom)
- karteziánska topológia: (kolektívna funkcia!)
MPI_Cart_create(oldcomm, ndims, sizeofdims, periods, mapping,
&newcomm)
kde oldcomm...definuje množinu úloh, kam bude nová topológia namapovaná
ndims...je počet dimenzií a sizeofdims...ich rozmery
periods...či je alebo nie je štruktúra periodická pre každú dimenziu
mapping...či je alebo nie je povolený reordering rankov (mapping
na fyzické procesory - optimalizácia)
newcomm...nový komunikátor s karteziánskou štruktúrou
Example 5:
máme 6 úloh (T0..T5) s rankami 0..5 a chceme zriadiť grid 2x3:
vytvoríme nový komunikátor 'gridcomm' a nepovolíme optimalizáciu mappingu:
MPI_Cart_create(MPI_COMM_WORLD, 2, {2,3}, {0,0}, 0, &newcomm)
rank v 'starej' skupine: 0 1 2 3 4 5
koordináty úlohy: [0,0] [0,1] [0,2] [1,0] [1,1] [1,2]
- zisťovanie koordinát a rankov (ak boli zmenené ranky pri mapping=true)
MPI_Cart_rank(communicator, coords, &rank)
kde coords je vektor rovnakého rozmeru ako prislúcha komunikátoru
MPI_Cart_coords(communicator, rank, ndims, &coords)
- topológia grafu
MPI_Graph_create(oldcomm, nnodes, index, edges, mapping, &newcomm)
int index[nnodes]; // suma počtu susedov pre uzly 0..i (degree)
int edges[...]; // indexy susedov uložené postupne za sebou
int mapping; // boolean - či je povolený renumbering rankov
Example 6:
máme 6 úloh (T0..T5) s rankami 0..5 a chceme zriadiť nasledovný graf:
0 int index[6] = {2, 5, 8, 12, 14, 16};
/ \ int edges[16]= {1, 2, 0, 3, 4, 0, 3, 5, 1, 2, 4, 5, 1, 3, 2, 3};
1 2 int mapping = 0;
|\ /| MPI_Comm *graphcomm; //má byť MPI_COMM_WORLD
| 3 | MPI_Graph_create(MPI_WORLD_COMM,nnodes,index,edges,mapping,graphcomm)
|/ \| vytvoríme nový komunikátor 'graphcomm' a nepovolíme optimalizáciu
4 5
Komunikácia medzi úlohami
- je typu message-passing
- pozostáva z komponentov: vysielač, prijímač (zvyčajne určené rankom),
prenášané údaje, označenie správy (tag),
komunikátor (ktorý poskytuje kontext pre komunikáciu)
- vysielací aj prijímací proces musia byť príslušné tomu istému komunikátoru
- komunikačné módy:
- standard send (blokujúci až kým si správu nepreberie zodpovedajúci
receive buffer alebo dočasný systémový buffer)
MPI_Send(buf, count, data_type, to_whom, tag, communicator)
+ ak je použitý buffer na uloženie odchádzajúcej správy, nečaká na
prijatie správy
- blocking receive (šdandardný príjem, proces čaká na správu)
MPI_Recv(buf, count, data_type, from_whom, tag, communicator, &status)
ak je použitá neurčitá špecifikácia (wild card) vysielača alebo tag-u,
hodnota status bude označovať MPI_SOURCE
(rank) aj MPI_TAG.
- buffered send - MPI_Bsend()
- na rozdiel od štandardného vysielania je
tu zaručené bufrovanie a teda neblokujúci charakter vysielania
- synchronous send - MPI_Ssend()
- čaká sa na začiatok vykonávania receive
na opačnej strane (najprv vyšle správu a potom čaká)
- ready send - MPI_Rsend()
- nevyšle správu skôr, než druhá strana
nezačala čakať na funkcii receive, nepočká však na jej začiatok
vykonávania (nielen že nepočká na jej ukončenie)
- neblokujúca komunikácia:
- úloha naštartuje send/receive a hneď pokračuje inou prácou. Neskôr si
zisťuje, v akom stave je prebiehajúci send/receive.
- môže byť kombinovaná s 'obyčajným' receive/send (nemusia byť párové)
- pozostáva z troch krokov:
- MPI_Isend(... +&request)
/ MPI_Irecv(... -&status +&request)
+ všetky modifikácie send-u (b,s,r)
- vykonávanie nejakého výpočtu
- MPI_Wait(request, &status)
.... status vráti doplňujúce info
MPI_Test(request, &flag, &status)
... flag je true, keď je hotovo
MPI_Testall(count, array_of_requests, &flag, &array_of_statuses)
MPI_Testany(count, array_of_requests, &index, &flag, &status)
MPI_Waitall(count, array_of_requests, &array_of_statuses)
MPI_Waitany(count, array_of_requests, &index, &status)
- ukončenie send operácie sa vyznačuje napr. tým, že je možné znovu použiť
vysielací buffer
- perzistentná (trvalá) komunikácia:
- redukuje sa komunikačná réžia
- zviaže sa zoznam parametrov s trvalým komunikačným request-om a potom sa
opakovane vyvoláva komunikačná funkcia s tým istým zoznamom parametrov
MPI_Send_init(), MPI_[BSR]send_init()
...parametre ako MPI_Isend(),
MPI_Recv_init()
...parametre ako MPI_Irecv()
- použije sa volaním MPI_Start(request)
resp. MPI_Startall(count, rq_array)
takto odštartovaná komunikácia je rovnaká ako ostatné neblokujúce
komunikácie
- MPI údajové typy:
- zahŕňa všetky bežné údajové typy z jazykov C a FORTRAN
- umožňuje ich použitie na heterogénnych strojoch (bit ordering...)
- odvodené typy - sekvencia údajových typov s celočíselnými zarovnaniami
(offset) elementov v sekvencii
- tak je réžia malá (aj pri komplikovaných elementoch)
MPI_BYTE, MPI_CHAR,
...(DOUBLE,FLOAT,INT,LONG,LONG_DOUBLE,SHORT),
MPI_PACKED,
...(UNSIGNED_CHAR,UNSIGNED,UNSIGNED_LONG,UNSIGNED_SHORT)
- polia základného typu sa dajú poslať v jednom príkaze (sú spojité)
- odvodené typy (aj pre nekontinuálne údaje rovnakého typu)
sa vytvárajú pomocou konštruktorov, je to sekvencia párov:
{(typ0,posun0), (typ1,posun1), ... (typ_n-1,posun_n-1)}
kde posun je v bajtoch. Nazýva sa aj ako typová mapa (type map).
MPI_Type_struct() môže byť použitá
aj rekurzívne:
MPI_Type_struct(count, block_lengths, displacements, array_types, newtype)
kde count ...........................je počet zoskupovaných blokov
int block_lengths[count] ...je počet elementov pre každý blok
int displacement[count] ...je posun pre každý blok
handle array_types[count] ...je pole deskriptorov údajových typov
handle newtype ...je nový deskriptor
- posuny môžu byť volené aj tak, že vzniknú diery
Synchronizácia
- slúži na zabezpečenie želaného poradia vykonávania v paralelnom programe
- precedenčná synchronizácia - blokujúci príjem
- komunikačné 'randevú' (rendezvous) - synchrónny send
- bariéra MPI_Barrier(communicator)
- napr. viac než 2 úlohy
všetky úlohy prislúchajúce komunikátoru musia vykonať bariéru
Example 9:
máme 5 úloh (T0..T4) s rankami 0..4 a chceme synchronizovať iba T2 s T3:
vytvoríme nový komunikátor 'newc' a "na ňom" vykonáme bariéru:
int ex[3]={0,1,4};
MPI_Group worldg,smallg;
MPI_Comm newc;
MPI_Comm_group(MPI_COMM_WORLD, &worldg);
MPI_Group_excl(worldg, 3, ex, &smallg);
MPI_Comm_create(MPI_COMM_WORLD, smallg, &newc);
MPI_Barrier(newc);
- otázka: môžu túto funkciu korektne vykonať všetky (5) úlohy?
Kolektívne operácie
- sú aplikované na všetkých členov skupiny, sú 3 typy:
- riadenie úloh - napr. MPI_Barrier()
- globálny výpočet (redukcia, čiastočná redukcia=scan)
- redukcia je asociatívna a komutatívna
predefinovaná (sum, min, max, ...) alebo 'user-defined'
výsledok môže byť doručený všetkým členom skupiny alebo len 'root'-ovi
- globálna kombinácia MPI_Reduce(sbuf, rbuf, n, data_type, op, rt, comm)
sbuf, rbuf ... adresa send/receive buffer-a
n...počet elementov (typu data_type) v sbuf
(Logical/Bitwise:)
op...operátor (MPI_SUM, PROD,MIN,MAX, LAND,LOR,LXOR, BAND,BOR,BXOR)
rt...rank 'root' úlohy (iba jej príde výsledok - do rbuf)
- 'many-to-many' MPI_Allreduce(sbuf, rbuf, n, data_type, op, comm)
výsledok sa uloží v každej úlohe do rbuf
- scan - 2 typy: prefix a postfix - výsledok je v každej úlohe iný,
podľa ranku:
pre úlohy T0...T9 ktoré držia údaje d0...d9 a operátor + bude
výsledok v T5 pri
prefixe d0+d1+...+d5
postfixe je d5+d6+...+d9
MPI_Scan(sbuf, rbuf, n, data_type, op, comm)
... prefix
- presun údajov (broadcast, scatter & gather)
- MPI_Bcast(&buffer, n, data_type, root, communicator)
...root->all
funkciu vykonajú všetky úlohy - nie ako v PVM (pvm_bcast->pvm_recv)
- scatter & gather
MPI_Scatter(sbuf, n, stype, rbuf, m, rtype, rt, communicator)
sbuf je rozporcovaný na kúsky o veľkosti n elementov a rozoslaný
MPI_Gather(sbuf, n, stype, rbuf, m, rtype, rt, communicator)
sbuf je poslaný na (rank)-tú pozíciu (veľkosť m)
do rbuf 'root'-a
- na komunikácii sa zúčastňuje aj root (rt)
- v Scatter má n význam len pre root-a (podobne v Gather m)
v Scatter n(root)==m(nonroot), stype(root)==rtype(nonroot)
v Gather m(root)==n(nonroot), rtype(root)==stype(nonroot)
Supervisor/worker application skeleton
#include "mpi.h"
void supervisor(), worker();
main(int argc, char *argv[]) {
int myrank;
MPI_Init(&argc,&argv);
MPI_Comm_rank(MPI_COMM_WORLD,&myrank);
if (myrank == 0)
supervisor();
else
worker();
MPI_Finalize();
exit(0);
}
void supervisor()
{int i, ntasks;
/* ... */;
MPI_Comm_size(MPI_COMM_WORLD, &ntasks);
for (i = 1; i < ntasks; ++i) {
/* send some work for worker i */;
}
for (i = 1; i < ntasks; ++i) {
/* receive results from worker with rank i */;
}
}
void worker()
{
/* ... */;
/* receive work from the supervisor (rank == 0) */;
/* send the result to the supervisor */;
}
}