6. Úvod do MPI
18. OpenMP + MPI – Spolupráce procesů a vláken¶
Úvod¶
-
OpenMP je paralelní programovací model pro sdílenou paměť a efektivně paralelizuje kód uvnitř jednoho stroje.
-
MPI (Message Passing Interface) je standard pro komunikaci mezi procesy, který umožňuje efektivní distribuované programování napříč více stroji.
-
Spojením OpenMP a MPI je možné vytvořit aplikace, které efektivně využívají paralelní výpočetní schopnosti na úrovni jednoho stroje i napříč více stroji.
MPI: Message Passing Interface¶
-
Co to je? MPI je standard pro komunikaci mezi procesy.
-
Jak funguje? Procesy si posílají zprávy a tak komunikují mezi sebou.
-
Kde se používá? Používá se v distribuovaných systémech, kde program běží na více uzlech řízených MPI knihovnou.
Skupiny Procesů v MPI¶
- Každý MPI proces je součástí skupiny a indexuje se od 0.
- MPI_Comm_rank: získá ID procesu v rámci skupiny.
- MPI_Comm_size: získá počet procesů v dané skupině.
- MPI_COMM_WORLD: výchozí skupina všech procesů v MPI programu.
Příklad kódu s MPI¶
#include <mpi.h>
#include <stdio.h>
int main(int argc, char** argv) {
MPI_Init(&argc, &argv);
int world_size;
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
int world_rank;
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
printf("Hello from process %d of %d\n", world_rank, world_size);
int c = 1; // privátní proměnná MPI procesu
// MPI_Allreduce ze vsech procesu secte c a ulozi ji do c tedy melo by to vyjit jako celkovy pocet procesu tedy world_size
MPI_Allreduce(MPI_IN_PLACE, &c, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
assert(c == world_size); // toto ověření se provede v každém MPI procesu
MPI_Finalize();
}
Překlad a Kompilace¶
Překlad programu s OpenMP¶
- Příklad:
g++ -fopenmp main.cpp
Překlad programu s MPI¶
- Příklad:
mpic++ main.cpp, nastavení konkrétního překladače:OMPI_CXX=g++
Spolupráce OpenMP a MPI¶
Proč je spolupráce potřebná?¶
- OpenMP je efektivní pro paralelizaci na úrovni jednoho uzlu, zatímco MPI je efektivní pro komunikaci mezi uzly. Spojením obou je možné dosáhnout vyššího výkonu v distribuovaných systémech s více procesory.
Možnosti tvorby programů na hybridních architekturách:¶
-
Čistý MPI model: V tomto přístupu, na každém jádru/procesoru/výpočetním uzlu běží jeden nebo několik málo MPI procesů. Procesy nevyužívají sdílenou paměť a nedělí se na vlákna. Tento model neefektivně využívá sdílenou paměť, ale je jednoduchý na implementaci.
-
Kombinovaný MPI a OpenMP model: V tomto modelu, na každém výpočetním uzlu/procesoru běží jeden nebo několik málo MPI procesů a ty se pomocí OpenMP dělí na několik vláken. Tyto vlákna jsou poté rozloženy na jednotlivá jádra. Tento model umožňuje efektivní využití sdílené paměti.
-
Hybridní model s 1 OpenMP vláknem na jádro: V tomto modelu, každý MPI proces na uzlu je rozdělen na více vláken (typicky jedno vlákno na jádro). Tento model je často efektivnější než čistý MPI model, protože umožňuje lepší využití sdílené paměti a méně komunikace mezi uzly.
Úrovně spolupráce mezi MPI a OpenMP:¶
-
MPI_THREAD_SINGLE: Pouze MPI, procesy se nedělí na vlákna. Je to nejjednodušší úroveň, kdy program funguje jako čistý MPI program bez vláken.
-
MPI_THREAD_FUNNELED: Procesy mohou být vícevláknové, ale pouze hlavní (master) vlákno může volat funkce MPI. Tento model je nazýván jako jednoportový model. Je to první úroveň, kdy můžeme kombinovat MPI a OpenMP.
-
MPI_THREAD_SERIALIZED: Procesy mohou být vícevláknové, ale v daném okamžiku smí funkce MPI volat pouze jedno vlákno. Je to stále jednoportový model, ale s možností volání MPI funkcí z různých vláken za předpokladu, že tato volání nekolidují v čase.
-
MPI_THREAD_MULTIPLE: Procesy mohou být vícevláknové a všechna vlákna mohou volat funkce MPI bez omezení. Tento model se nazývá víceportový model. Je to nejsofistikovanější a nejflexibilnější úroveň, ale může vyžadovat složitější ladění a synchronizaci.
Příklad kódu s MPI_THREAD_FUNNELED:¶
V následujícím příkladu je demonstrováno použití úrovně MPI_THREAD_FUNNELED.
#include <iostream>
#include <mpi.h>
#include <stdexcept>
#include <omp.h>
int main(int argc, char* argv[]) {
int provided, required = MPI_THREAD_FUNNELED;
MPI_Init_thread(&argc, &argv, required, &provided);
if (provided < required) {
throw std::runtime_error("MPI library does not provide required threading support");
}
int proc_num;
MPI_Comm_rank(MPI_COMM_WORLD, &proc_num);
#pragma omp parallel
{
std::cout << "Process " << proc_num << " Thread " << omp_get_thread_num() << std::endl;
}
MPI_Finalize();
return 0;
}
Tento kód inicializuje MPI s požadovanou úrovní MPI_THREAD_FUNNELED a poté využívá OpenMP k vytvoření paralelní sekce, kde každé vlákno vypíše své ID a ID procesu, ke kterému patří.
19. MPI: blokující komunikační operace a jejich komunikační módy, stavové objekty.¶
Blokující vs. Neblokující Komunikační Operace:¶
- Blokující operace: MPI funkce se dokončí až po dosažení určitého stavu v komunikaci.
- Neblokující operace: MPI funkce se dokončí okamžitě a stav komunikace musí být následně testován.
Druhy Komunikačních Operací:¶
- 2-bodové (Point-to-point): Komunikace mezi dvěma MPI procesy.
- Kolektivní (Collective): Komunikace mezi všemi procesy asociovanými s daným komunikátorem.
Funkce MPI_Send (2-bodová komunikační operace):¶
- Profil:
int MPI_Send(const void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm); -
Parametry:
buf: Ukazatel na odesílaná data.count: Počet odesílaných položek.datatype: Datový typ odesílaných dat (např.MPI_INT,MPI_FLOAT).dest: Číslo cílového procesu.tag: Značka zprávy, která umožňuje určit typ/kategorii zprávy.comm: MPI komunikátor (nejčastějiMPI_COMM_WORLD).
-
Komunikační módy:
- Standardní mód: Funkce
MPI_Sendmůže ukončit po kopírování dat do bufferu nebo po jejich přijetí cílovým procesem. To závisí na implementaci MPI. - Buffered mode: Data jsou překopírována do bufferu a funkce se ukončí bez ohledu na to, zda byla data přijata cílovým procesem. Uživatel musí připravit buffer pomocí
MPI_Buffer_attach. Používá se funkceMPI_Bsend. - Synchronous mode: Funkce
MPI_Ssendse ukončí až po přijetí dat cílovým procesem. - Ready mode: Funkce
MPI_Rsendvyžaduje, aby cílový proces byl připraven přijímat data, jinak vrátí chybu.
- Standardní mód: Funkce
Funkce MPI_Recv (2-bodová komunikační operace):¶
- Profil:
int MPI_Recv(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status); - Parametry:
buf: Ukazatel na buffer pro přijímaná data.count: Maximální počet přijímaných položek.datatype: Datový typ přijímaných dat.source: Číslo zdrojového procesu.tag: Značka zprávy.comm: MPI komunikátor.status: Ukazatel na stavový objekt, který obsahuje informace o přijaté zprávě (např. zdroj a tag).
Stavové Objekty v MPI:¶
Stavové objekty v MPI poskytují informace o přijatých zprávách. Tyto objekty obsahují informace, jako je zdrojový proces, tag zprávy, a další užitečné údaje. Stavové objekty jsou často využívány při použití nepojmenovaných příjmacích operací (například MPI_Recv), kde příjemce nemusí předem vědět, od koho nebo jaký druh zprávy obdrží.
Hlavní části stavového objektu v MPI jsou:
- MPI_SOURCE: Identifikátor procesu, od kterého byla zpráva odeslána.
-
MPI_TAG: Etiketa (tag) zprávy, která může být užitečná pro rozeznání typu přijatých dat.
-
Pomoci funkce
MPI_Get_countlze ze stavového objektu rovněž získat velikost zprávy (= počet přijatých prvků).
20. MPI Neblokující Komunikační Operace a Způsoby Zjištění Jejich Dokončení, Stavové Objekty¶
Neblokující Komunikační Operace:¶
- Neblokující komunikační operace v MPI umožňují, aby komunikace probíhala na pozadí, zatímco hlavní program pokračuje v dalších výpočtech.
- Neblokující operace se iniciují funkcemi jako
MPI_Isend,MPI_Ibsend,MPI_Issend,MPI_Irsend(pro odesílání) aMPI_Irecv(pro příjem). - Po iniciaci neblokující komunikační operace nesmí být buffer modifikován, dokud není operace explicitně dokončena.
MPI_Request a Zjištění Dokončení Operace:¶
- Neblokující operace používají dodatečný parametr typu
MPI_Request, který je použit k zaznamenání stavu operace. - Existují různé funkce pro testování nebo čekání na dokončení neblokujících operací:
- MPI_Test: Nečekající testování dokončení operace.
- MPI_Wait: Blokující čekání na dokončení operace.
- MPI_Testany a MPI_Waitany: Testování/čekání na dokončení libovolné operace z množiny.
- MPI_Testall a MPI_Waitall: Testování/čekání na dokončení všech operací z množiny.
Stavové Objekty u Neblokujících Operací:¶
- Stavové objekty (
MPI_Status) u neblokujících příjmů se získávají až z funkcíMPI_TestneboMPI_Wait, nikoliv z funkceMPI_Irecv. - Pomocí stavových objektů lze zjistit, odkud zpráva přišla, jakou měla značku a kolik položek bylo přijato.
Význam Neblokujících Komunikačních Operací:¶
- Důležité pro korektnost a výkonnost paralelních programů.
- Umožňuje překrývání komunikace a výpočtu.
- Může předejít problémům se zablokováním v komplexních komunikačních schématech.
- Umožňuje dynamická komunikační schémata bez nutnosti serializace volání.
21. MPI: Sondování příchodu zprávy¶
Obecně o sondování¶
Sondování v MPI umožňuje procesu zjistit, zda existuje zpráva k přijetí, aniž by ji skutečně přijal.
Funkce sondování¶
-
MPI_Iprobe: Neblokující verze sondování, která umožňuje procesu pokračovat ve své práci, aniž by čekal na příchod zprávy. -
MPI_Probe: Blokující verze sondování, která zastaví proces, dokud není zpráva k přijetí k dispozici.
Parametry sondovacích funkcí¶
source: Identifikátor zdrojového procesu, odkud se očekává zpráva. Může být nastaven naMPI_ANY_SOURCEpro přijetí zprávy od libovolného procesu.tag: Identifikátor typu zprávy. Může být nastaven naMPI_ANY_TAGpro přijetí libovolného typu zprávy.comm: Komunikační skupina, v rámci které se má sondování provést.flag: Výstupní parametr, který dostane hodnotutrue, pokud je zpráva k dispozici, jinakfalse.status: Výstupní parametr obsahující informace o stavu zprávy.
Sondování s rezervací¶
MPI poskytuje funkci MPI_Improbe, která umožňuje rezervovat zprávu pro budoucí přijetí. Tím se zabraňuje možnému konfliktu mezi vlákny o přijetí téže zprávy.
-
MPI_Improbevrátí tzv.message handlev parametrumessage, pokud je zpráva k dispozici. -
MPI_Mrecvlze použít pro přijetí zprávy na základěmessage handle.
Použití sondování¶
-
Přijetí volitelných zpráv, např. ukončení výpočtu při nalezení optimálního řešení.
-
Přijetí zpráv s neznámou velikostí: Nejprve použitím sondování zjistíme velikost zprávy, alokujeme buffer a poté přijmeme zprávu pomocí
MPI_Recv.
22. MPI: Návratové Hodnoty Funkcí a Ošetření Chyb¶
Indikace chyb v programu:¶
MPI standard předpokládá, že komunikace je spolehlivá a odeslaná zpráva je vždy správně přijata. Neposkytuje mechanismy pro řešení chyb komunikačního systému, ale očekává, že implementace MPI bude izolovat uživatele od nespolehlivostí komunikační infrastruktury.
Ošetření chyb:¶
Téměř každá MPI funkce vrací návratovou hodnotu, která indikuje úspěšnost/neúspěšnost jejího provedení. Pokud je funkce úspěšně provedena, vrátí hodnotu MPI_SUCCESS. Pokud dojde k chybě, vrátí chybový kód.
Obsluha chyb:¶
Chybový kód je základem pro obsluhu chyby (error handler) dané MPI funkce. MPI poskytuje několik předdefinovaných obsluh chyb a umožňuje uživatelům vytvářet vlastní.
Předdefinované obsluhy chyb:
-
MPI_ERRORS_ARE_FATAL: V případě chyby násilně ukončí celý MPI program (všechny procesy) volánímMPI_ABORT. Tato obsluha je implicitně asociovaná s komunikátoremMPI_COMM_WORLD. -
MPI_ERRORS_RETURN: Vrací chybový kód MPI funkce, ale nestanoví stav MPI výpočtu po chybě, což závisí na implementaci. -
MPI_ERRORS_ABORT(od MPI 4.0): Způsobí násilné ukončení pouze procesů spojených s daným komunikátorem, nikoli všech procesů.
Uživatel může také vytvořit vlastní obsluhu chyby pomocí funkcí jako MPI_Comm_create_errhandler, MPI_Comm_set_errhandler, MPI_Comm_get_errhandler, a MPI_Errhandler_free.
Nastavení:¶
Předdefinovanou nebo uživatelem definovanou obsluhu chyb lze nastavit na komunikátor pomocí funkce MPI_Comm_set_errhandler. Například:
MPI_Comm_set_errhandler(MPI_COMM_WORLD, MPI_ERRORS_RETURN);
int error_code;
error_code = MPI_Send(..., MPI_COMM_WORLD);
if (error_code == MPI_ERR_COMM) {
// ošetření neplatného komunikátoru
} else if (error_code == MPI_ERR_COUNT) {
// ošetření neplatné velikosti zprávy
} else if (error_code == MPI_ERR_TAG) {
// ošetření neplatného TAGu zprávy
}
Tento kód ukazuje, jak nastavit obsluhu chyb tak, aby MPI funkce vrátila chybový kód, a jak jej následně zpracovat.
23. MPI: Implementace Permutace Cyklický Posuv¶
Úvod¶
- Permutace cyklický posuv je často používaná komunikační operace v MPI, kde proces
proc_numpošle zprávu procesu(proc_num + 1) % num_procs.
Nefunkční Kód¶
- Použití
MPI_SendaMPI_Recvmůže vést k deadlocku (zablokování), pokud všechny procesy čekají na dokončení odesílání a nepřistoupí k přijímání.
Korektní Řešení I: Odd-Even Communication¶
- Sudé procesy nejprve odesílají data lichým a poté přijímají data. Liché procesy dělají opak.
- Toto řešení funguje i pro lichý počet procesů.
int next = (proc_num + 1) % num_procs;
int prev = (proc_num + num_procs - 1) % num_procs;
bool odd = proc_num % 2;
if (!odd) {
MPI_Send(&value_to_be_sent, 1, MPI_INT, next, 0, MPI_COMM_WORLD);
MPI_Recv(&value_to_be_received, 1, MPI_INT, prev, ...);
} else {
MPI_Recv(&value_to_be_received, 1, MPI_INT, prev, ...);
MPI_Send(&value_to_be_sent, 1, MPI_INT, next, 0, MPI_COMM_WORLD);
}
Korektní Řešení II: Buffered Send¶
- Použití
MPI_Bsendumožňuje, aby funkce skončila bez čekání na iniciaci příjmu, protože data jsou uložena v bufferu.
int next = (proc_num + 1) % num_procs;
int prev = (proc_num + num_procs - 1) % num_procs;
int buf[1], *bptr, bs;
MPI_Buffer_attach(buf, sizeof(int) + MPI_BSEND_OVERHEAD);
MPI_Bsend(&value_to_be_sent, 1, MPI_INT, next, 0, MPI_COMM_WORLD);
MPI_Recv(&value_to_be_received, 1, MPI_INT, prev, ...);
MPI_Buffer_detach(&bptr, &bs);
Korektní Řešení III: Non-blocking Send¶
- Použití
MPI_Isendumožňuje okamžité ukončení funkce, protože odesílání je neblokující. Musíte použítMPI_Waitk čekání na dokončení komunikace.
int next = (proc_num + 1) % num_procs;
int prev = (proc_num + num_procs - 1) % num_procs;
MPI_Request request;
MPI_Isend(&value_to_be_sent, 1, MPI_INT, next, 0, MPI_COMM_WORLD, &request);
MPI_Recv(&value_to_be_received, 1, MPI_INT, prev, ...);
MPI_Wait(&request, MPI_STATUS_IGNORE);
Korektní Řešení IV: Send and Receive¶
MPI_Sendrecvumožňuje procesu současně odeslat a přijmout data.- Je to obvykle praktické a efektivní řešení, protože minimalizuje čekání a synchronizaci mezi procesy.
int next = (proc_num + 1) % num_procs;
int prev = (proc_num + num_procs - 1) % num_procs;
MPI_Sendrecv(
&value_to_be_sent, 1, MPI_INT, next, 0,
&value_to_be_received, 1, MPI_INT, prev, 0,
MPI_COMM_WORLD, MPI_STATUS_IGNORE
);