Programowanie Równoległe i Rozproszone Lucjan Stapp Wydział Matematyki i Nauk Informacyjnych Politechnika Warszawska (l.stapp@mini.pw.edu.pl) 1/54 PRiR - wstęp Parallel Virtual Machine() zestaw narzędzi do tworzenia oprogramowania dla sieci równolegle połączonych sekwencyjnych i/lub równoległych komputerów. Został zaprojektowany i stworzony by umożliwić łączenie komputerów o różnych konfiguracjach sprzętowych w jeden równolegle działający komputer. Stworzony w latach 90- tych; Do dziś jest to aktywny projekt: http://www.csm.ornl.gov/pvm/. Aktualna wersja to 3.4.6 z 2001 r. 2/54 PRiR Parallel Virtual Machine() nie jest wg. dzisiejszych definicji maszyna wirtualną. To zarządca zrównoleglonych procesów. 3/54 PRiR 1
umożliwia stworzenie" z pewnej ilości komputerów (mogą być sekwencyjne i/lub równoległe) jednego równoległego systemu. Podstawowe zasady: Użytkownik definiuje zbiór maszyn, na których wykonywany jest program. Zbiór ten jest dynamiczny (można dodawać i/lub usuwać węzły w trakcie działania); Przezroczystość sprzętowa; Przekazywanie danych w oparciu o model przekazywanie komunikatów (message passing); Heterogeniczność systemu; Wspomaganie wieloprocesowości. 4/54 PRiR Schemat działania 5/54 PRiR umożliwia stworzenie" z pewnej ilości komputerów (mogą być sekwencyjne i/lub równoległe) w jeden równoległy. Odbywa się to na zasadzie dołączania kolejnych węzłów (komputerów), na których został zainstalowany i odpowiednio skonfigurowany. Podłączanie węzła składa się z grubsza z 3 etapów: 1. połączenia przez remote shell rsh, 2. uruchomieniu demona pvmd3 (dalej oznaczanego jako pvmd) 3. dostarczenia demonowi informacji o parametrach istniejącej sieci. 6/54 PRiR 2
Schemat architektury 7/54 PRiR można używać pisząc programy w różnych językach, tu omówione zostanie ich zastosowanie w językach C i C++. Standardowo, mamy do dyspozycji zestaw funkcji z parametrami. Umożliwia to przekazywanie argumentów jako wartości zmiennych i/lub wskaźników oraz zwracanie przez funkcje odpowiednich wartości Dodatkowo, mamy do dyspozycji zestaw makr i stałych systemowych (np. errno i pvm_errno do obsługi błędów) ułatwia to rozróżnianie różnych wyjść. 8/54 PRiR Każdemu procesowi (zwanemu zadaniem task) w momencie rejestracji nadawany jest jego unikalny numer typu integer (task identifier - TID). Ten identyfikator jest niezależny zarówno od sprzętu, na którym pracuje proces jak i od architektury systemu. Identyfikator jest unikalny i może być uważany za adres procesu. Identyfikator można wykorzystywać przy różnych procedurach - można przesłać informacje pod dany adres, można zdalnie "zabić" proces o danym identyfikatorze itp. 9/54 PRiR 3
Czasami pracujemy w grupach zadań. Każdej takiej grupie można nadać nazwę (numer). Zadanie otrzymuje też swój unikalny identyfikator w grupie. Identyfikatory w grupie zawsze zaczynają się od zera i stopniowo rosną. Mamy wydzielony zestaw procedur odnoszący się do grup. Nie zostaną one omówione w ramach tego wykładu. 10/54 PRiR startowy program #include "pvm3.h main() int cc, tid, msgtag; char buf[100]; printf( to ja t%x\n", pvm_mytid()); /* pobranie identyfikatora funkcją pvm_mytid */ cc = pvm_spawn( Czesc wszystkim", (char**)0, 0, "", 1,&tid); /*rozesłanie info funkcją pvm_spawn */ if (cc == 1) /*jeżeli spawn się udał */ msgtag = 1; pvm_recv(tid, msgtag); /*odebranie od innych */ pvm_upkstr(buf); printf( od t%x: %s\n", tid, buf); else printf( nie można wykonac czesc wszystkim \n"); pvm_exit(); /*wyjście z pvm*/ 11/54 PRiR #include "pvm3.h main() int ptid, msgtag; char buf[100]; ptid = pvm_parent(); /* pobranie adresu rodzica */ strcpy(buf, czesc wszytkim odebrane od"); gethostname(buf + strlen(buf), 64); msgtag = 1; pvm_initsend(pvmdatadefault); pvm_pkstr(buf); pvm_send(ptid, msgtag); /* te 3 instrukcje to przesłanie odpowiedzi: inicjalizacja bufora do przesłania umieszczenie danych wysłanie */ pvm_exit(); /*wyjście z pvm*/ odbiorca 12/54 PRiR 4
W rozróżniamy trzy możliwe organizacje zadań, co umożliwia nam różne strategie wykonywania obliczeń: Najbardziej popularny model to crowd computing : wiele procesów, na ogół wykonujących ten sam kod, z wymianą informacji poprzez przesyłanie pomiędzy procesami odpowiednich wiadomości Model master slave ; Model jeden nadzorca jedno ręcznie wystartowane zadanie odpowiada za podział zadań, reszta liczy; tree computation najczęściej stosowany dla algorytmów typu divide-and-conquer ; model hybrydowy ; 13/54 PRiR Schemat architektury Master slave nie ma komunikacji między niewolnikami 14/54 PRiR schemat mastera /*rozmieszczenie początkowe */ for (i=0; i < NumWorkers; i++ ) pvm_spawn(<worker name>); // Uruchomienie niewolnika i pvm_send(<worker tid i>,999); /* przesłanie zadania do niewolnika */ /* Odbieranie - przesyłanie */ while (WorkToDo) pvm_recv(888) // Odbieranie wyników pvm_send(<available worker tid>,999) /* przesłanie kolejnego zadania do available worker /* zebranie wyników */ for (i=0; i < NumWorkers; i++ ) pvm_recv(888) // Odebranie wynikow pvm_kill(<worker tid i>)//zakończenie pracy niewolnika i 15/54 PRiR 5
schemat niewolnika while (1) pvm_recv(999); //pobranie zadania result = Calculations(task;)//obliczenia pvm_send(<master tid>,888); // Przeslanie wyników do mastera Uwaga: W podanym schemacie nie ma komunikacji między niewolnikami. 16/54 PRiR schemat drzewa W tym podejściu zadania (procesy) są tworzone dynamicznie w czasie wraz z postępem obliczeń. Równocześnie tworzona jest relacja ojciec dziecko (jak w drzewie). Podejście to różni się od poprzedniego właśnie tą budową relacji (w master slave jest to raczej gwiazda). Jest głównie stosowane, gdy nie znamy z góry liczby potrzebnych procesów (np. WBS, devide and conquer). 17/54 PRiR int tid = pvm_mytid( void ) sterowanie Procedura pvm_mytid() zwraca identyfikator TID. Może być wywoływana wielokrotnie. Przyjęło się ją wywoływać jako pierwszą, by równocześnie zapewnić wpisanie tego procesu do. int info = pvm_exit( void ) Procedura pvm_exit() informuje lokalnego demona że proces opuszcza. Nie zabija procesu. Na ogół używana przed zakończeniem programu. 18/54 PRiR 6
sterowanie int numt = pvm_spawn(char *task, char **argv, int flag, char *where, int ntask, int *tids ) Procedura pvm_spawn() uruchamia ntask kopii pliku wykonalnego na maszynie wirtualnej. Jej podstawowe parametry to: argv wskaźnik do tablicy z parametrami (może być NULL), flag specyfikuje opcje. pvm_spawn zwraca, ile kopii udało się uruchomić. int info = pvm_kill( int tid ) Zabija proces on identyfikatorze tid. int info = pvm_catchout( FILE *ff ) Zbiera i wysyła na wyjście wyniki od poprzednio uruchomionych dzieci procesu. 19/54 PRiR informacje int tid = pvm_parent( void ) Zwraca tid rodzica. int info = pvm_config( int *nhost, int *narch, struct pvmhostinfo **hostp Zwraca informacje o maszynie wirtualnej, takie jak ilość węzłów nhost, hostp jest wskaźnikiem do utworzonej przez użytkownika tablicy pvmhostinfo o zadanej strukturze, która musi być wielkości >= nhost. Otrzymujemy w niej m.in. informacje o demonach, nazwach węzłów, a także o relatywnej prędkości CPU maszyny, na której uruchamiana jest ta instrukcja. 20/54 PRiR operacje dodawania i usuwania int info = pvm_addhosts( char **hosts, int nhost, int *infos) int info = pvm_delhosts( char **hosts, int nhost, int *infos) Dodanie i usunięcie węzła. 21/54 PRiR 7
przekazywanie danych int bufid = pvm_initsend( int encoding ) Wywoływana przed przesłaniem informacji. Czyści bufor (lub tworzy nowy) do przesłania informacji. Zwraca identyfikator bufora. Dopuszczalne wartości encoding to: PvmDataDefault - kodowanie XDR (external Data Representation) http://www.cisco.com/en/us/docs/ios/sw_upgrades /interlink/r2_0/rpc_pr/rpxdesc.html; PvmDataRaw brak kodowania; PvmDataInPlace w buforze są tylko informacje o wielkości danych i wskaźnik na miejsce, gdzie te dane są umieszczone. Przy przesyłaniu (pvm_send()) następuje kopiowanie bezpośrednio do pamięci odbiorcy. 22/54 PRiR pakowanie danych int info = pvm_pkbyte( char *cp, int nitem, int stride ) int info = pvm_pkcplx( float *xp, int nitem, int stride ) int info = pvm_pkdcplx( double *zp, int nitem, int stride ) int info = pvm_pkdouble( double *dp, int nitem, int stride ) int info = pvm_pkfloat( float *fp, int nitem, int stride ) int info = pvm_pkint( int *np, int nitem, int stride ) int info = pvm_pklong( long *np, int nitem, int stride ) int info = pvm_pkshort( short *np, int nitem, int stride ) int info = pvm_pkstr( char *cp ) Powyższe procedury pakują macierz danych danego typu do aktywnego bufora. Można włożyć wiele różnych typów danych, wywołując te procedury wielokrotnie. Dane są wyjmowane po przesłaniu w tej samej kolejności co zostały włożone; nitem oznacza ilość danych. 23/54 PRiR przekazywanie danych int info = pvm_send( int tid, int msgtag ) Natychmiastowe przesłanie danych do procesu TID int info = pvm_mcast( int *tids, int ntask, int msgtag ) Rozesłanie informacji metoda broadcastdo wszystkich procesów o TID w tablicy ntasks. 24/54 PRiR 8
przekazywanie danych int bufid = pvm_recv( int tid, int msgtag ) Blokowany odbiór; odbiorca czeka, aż coś będzie od nadawcy tid. int bufid = pvm_nrecv( int tid, int msgtag ) Nieblokowany odbiór; jeżeli nie ma co odebrać, zwraca 0. int bufid = pvm_probe( int tid, int msgtag ) Warto używać przy nieblokowanym odbiorze; sprawdza zawartość bufora. int info = pvm_bufinfo( int bufid, int *bytes, int *msgtag, int *tid ) Sprawdzenie, co jest w buforze bufid. 25/54 PRiR /* Fork Join Example Demonstrates how to spawn processes and exchange messages */ /* defines and prototypes for the library */ #include <pvm3.h> /* Maximum number of children this program will spawn */ #define MAXNCHILD 20 /* Tag to use for the joing message */ #define JOINTAG 11 int main(int argc, char* argv[]) /* number of tasks to spawn, use 3 as the default */ int ntask = 3; /* return code from pvm calls */ int info; /* my task id */ int mytid; /* my parents task id */ int myparent; /* children task id array */ int child[maxnchild]; int i, mydata, buf, len, tag, tid; /* find out my task id number */ mytid = pvm_mytid(); /* check for error */ if (mytid < 0) /* print out the error */ pvm_perror(argv[0]); /* exit the program */ return -1; przykład 26/54 PRiR przykład /* find my parent's task id number */ myparent = pvm_parent(); /* exit if there is some error other than PvmNoParent */ if ((myparent < 0) && (myparent!= PvmNoParent)) pvm_perror(argv[0]); pvm_exit(); return -1; /* if I don't have a parent then I am the parent */ if (myparent == PvmNoParent) /* find out how many tasks to spawn */ if (argc == 2) ntask = atoi(argv[1]); /* make sure ntask is legal */ if ((ntask < 1) (ntask > MAXNCHILD)) pvm_exit(); return 0; /* spawn the child tasks */ info = pvm_spawn(argv[0], (char**)0, PvmTaskDefault, (char*)0, ntask, child); 27/54 PRiR 9
/* print out the task ids */ for (i = 0; i < ntask; i++) if (child[i] < 0) /* print the error code in decimal*/ printf(" %d", child[i]); else /* print the task id in hex */ printf("t%x\t", child[i]); putchar('\n'); /* make sure spawn succeeded */ if (info == 0) pvm_exit(); return -1; /* only expect responses from those spawned correctly */ ntask = info; for (i = 0; i < ntask; i++) /* recv a message from any child process */ buf = pvm_recv(-1, JOINTAG); if (buf < 0) pvm_perror("calling recv"); info = pvm_bufinfo(buf, &len, &tag, &tid); if (info < 0) pvm_perror("calling pvm_bufinfo"); info = pvm_upkint(&mydata, 1, 1); if (info < 0) pvm_perror("calling pvm_upkint"); if (mydata!= tid) printf("this should not happen!\n"); printf("length %d, Tag %d, Tid t%x\n", len, tag, tid); przykład 28/54 PRiR przykład pvm_exit(); return 0; /* i'm a child */ info = pvm_initsend(pvmdatadefault); if (info < 0) pvm_perror("calling pvm_initsend"); pvm_exit(); return -1; info = pvm_pkint(&mytid, 1, 1); if (info < 0) pvm_perror("calling pvm_pkint"); pvm_exit(); return -1; info = pvm_send(myparent, JOINTAG); if (info < 0) pvm_perror("calling pvm_send"); pvm_exit(); return -1; pvm_exit(); return 0; 29/54 PRiR Więcej przykładów: przykłady http://www.netlib.org/pvm3/book/node55.html 30/54 PRiR 10
MPI - wstęp Message PassingInterface (MPI) protokół komunikacyjny będący standardemprzesyłania komunikatów pomiędzy procesami programów równoległych działających na jednym lub więcej komputerach. Interfejs ten wraz z protokołem oraz semantyką specyfikuje, jak jego elementy winny się zachowywać w dowolnej implementacji. Celami MPI są wysoka jakość, skalowalnośćoraz przenośność. MPI jest dominującym modelem wykorzystywanym obecnie w klastrach komputerów oraz superkomputerach. http://pl.wikipedia.org/wiki/message_passing_interface 31/54 PRiR MPI - wstęp Obecnie występują 2 najpopularniejsze wersje: wersja 1.2 (tj. MPI-1, gotowy w maju 1994), która kładzie nacisk na przekazywanie wiadomości oraz jest zaopatrzona w statyczne środowisko uruchomieniowe oraz MPI-2.1 (MPI-2, ukończona w 1998), która zawiera kilka dodatków jak równoległe I/O, dynamiczne zarządzanie procesami oraz zarządzanie operacjami pamięci. Specyfikacja MPI-2 zawiera ponad 500 funkcji dla ANSI C, ANSI Fortran (Fortran90) oraz ANSI C++. Warto zauważyć, iż MPI-2 jest głównie nadzbiorem MPI-1, mimo iż część funkcji została wymieniona. Programy napisane w standardzie 1.2 są kompatybilne z MPI-2. http://pl.wikipedia.org/wiki/message_passing_interface 32/54 PRiR MPI - wstęp Po wprowadzeniu, nowszy standard nie cieszył się dużą popularnością, ponieważ rok wcześniej opracowano MPICH, w którym zaimplementowano część poprawek wprowadzanych w MPI-2. MPICH to najczęściej stosowana implementacja MPI. MPICH to ogólnodostępna, darmowa i przenośna implementacja standardu MPI. Pozwala na przekazywanie komunikatów pomiędzy aplikacjami działającymi równolegle. Nadaje się do stosowania na małych klastrach. http://pl.wikipedia.org/wiki/message_passing_interface 33/54 PRiR 11
/* "Hello World" MPI Test Program */ #include <mpi.h> #include <stdio.h> #include <string.h> #define BUFSIZE 128 #define TAG 0 int main(int argc, char *argv[]) char idstr[32]; char buff[bufsize]; int numprocs; int myid; int i; MPI_Status stat; /* MPI programs start with MPI_Init; all 'N' processes exist thereafter */ MPI_Init(&argc,&argv); /* find out how big the MPI world is */ MPI_Comm_size(MPI_COMM_WORLD,&numprocs); /* and this processes' rank is */ MPI_Comm_rank(MPI_COMM_WORLD,&myid); MPI przykład 34/54 PRiR MPI przykład /* At this point, all programs are running equivalently, the rank distinguishes the roles of the programs in the model, with rank 0 often used specially... */ if(myid == 0) printf("%d: We have %d processors\n", myid, numprocs); for(i=1;i<numprocs;i++) printf(buff, "Hello %d! ", i); MPI_Send(buff, BUFSIZE, MPI_CHAR, i, TAG, MPI_COMM_WORLD); for(i=1;i<numprocs;i++) MPI_Recv(buff, BUFSIZE, MPI_CHAR, i, TAG, MPI_COMM_WORLD, &stat); printf("%d: %s\n", myid, buff); else /* receive from rank 0: */ MPI_Recv(buff, BUFSIZE, MPI_CHAR, 0, TAG, MPI_COMM_WORLD, &stat); printf(idstr, "Processor %d ", myid); strncat(buff, idstr, BUFSIZE-1); strncat(buff, "reporting for duty\n", BUFSIZE-1); /* send to rank 0: */ MPI_Send(buff, BUFSIZE, MPI_CHAR, 0, TAG, MPI_COMM_WORLD); /* MPI programs end with MPI Finalize; this is a weak synchronization point */ MPI_Finalize(); return 0; 35/54 PRiR MPI - wstęp MPI jest z wyglądu bardzo podobna do. Podstawowa różnica tkwi w modelu obliczeń. W większości implementacji MPI, stały zbiór procesów jest tworzony na początku obliczeń, przy czym obowiązuje zasada; jeden proces jeden procesor. MPI jest raczej podejściem typu MIMD, jakkolwiek można go też używać zlecając wszystkim elementom wykonywanie tego samego oprogramowania, uzależnionego od parametrów symetria kodu. Główny nacisk w MPI jest położony na mechanizmy komunikacyjne pomiędzy procesami. Dopuszczamy dwa rodzaje komunikacji: point-to-point pomiędzy dwoma konkretnymi procesami; komunikacja zbiorowa (collective) np. broadcast. 36/54 PRiR 12
MPI - wstęp MPI umożliwia sprawdzanie wiadomości ułatwia to komunikacje asynchroniczną. W MPI mamy możliwość wykorzystywania komunikacji modularnej przy pomocy mechanizmu zwanego komunikatorem (communicator). Mechanizm ten umożliwia programistom tworzenie modułów, które zawierają wewnętrzne struktury komunikacyjne. 37/54 PRiR MPI - wstęp W MPI łatwo oprogramowywuje się algorytmy, w których wyraźne jest powiązanie pomiędzy procesem a procesorem. Można tu wykorzystać komunikację typu point-to-point (lub komunikację zbiorową), by zapewnić odpowiednie przesyłanie komunikatów. Algorytmy, które tworzą zadania dynamicznie (lub wymuszają kilka procesów na procesorze) powinny być odpowiednio modyfikowane (poprawiane??), by można je było uruchomić używając MPI. Jeżeli jest to trudne (lub nie potrafimy tego zrobić), MPI nie jest dobrym narzędziem. 38/54 PRiR MPI - wstęp Na starcie wszystkie uruchomione procesy są elementami standardowego komunikatora globalnego MPI_COMM_WORLD. 39/54 PRiR 13
MPI - jądro Podstawą MPI jest 6 funkcji: MPI_INIT: Rozpoczyna obliczenia w MPI. MPI_FINALIZE: Kończy obliczenia. MPI_COMM_SIZE: Określa liczbę procesów. MPI_COMM_RANK: Zwraca identyfikator my_process (globalny lub lokalny) MPI_SEND: Nadanie wiadomości. MPI_RECV: Odebranie wiadomości. Przy pomocy tych 6 funkcji można napisać większość programów wykorzystujących MPI. 40/54 PRiR Podstawą MPI jest 6 funkcji: MPI - przykład void main() MPI_INIT(); // Rozpoczęcie obliczeń MPI_COMM_SIZE(MPI_COMM_WORLD, count); // ile jest procesów MPI_COMM_RANK(MPI_COMM_WORLD, myid) // określenie mego ID print("i am", myid, "of", count) MPI_FINALIZE() //Koniec Typowy start. 41/54 PRiR Podstawą MPI jest 6 funkcji: void main() MPI przykład 2 MPI_INIT(); // Rozpoczęcie obliczeń MPI_COMM_SIZE(MPI_COMM_WORLD, count) // ile jest procesów if (count!=2) return (-1); // muszą być dwa procesy MPI_COMM_RANK(MPI_COMM_WORLD, myid); // określenie mego ID if (myid=0) my_send(100); else my_receive(100); MPI_FINALIZE(); //Koniec void my_send( int ile) for(int i=0, i<ile, i++) MPI_SEND (i,1,mpi_int,1,0,mpi_comm_world); /*przesłanie ile razy po jednym int do procesu o id=0 w MPI_COMM_WORLD*/ i= - 1; MPI_SEND (i,1,mpi_int,1,0,mpi_comm_world); /*koniec danych */ 42/54 PRiR 14
Podstawą MPI jest 6 funkcji: MPI przykład 2 void my_receive( int ile) MPI_RECEIVE (msg,1,mpi_int,0,0,mpi_comm_world, status); while (msg!=-1) zrob_cos(msg) MPI_RECEIVE (msg,1,mpi_int,0,0,mpi_comm_world, status); /*koniec danych */ Dwuprocesorowe przekazanie wiadomości. 43/54 PRiR MPI operacje globalne Operacje globalne: Barrier: Synchronizacja wszystkich procesów Broadcast: Wysłanie danych z jednego procesu do wszystkich pozostałych. Gather: Pobranie danych z wszystkich procesów do jednego. Scatter: Rozpraszanie danych z jednego procesu do pozostałych. Reduction operations: Suma, mnożenie itp. rozproszonych danych. Ułatwiają tworzenie bardziej skomplikowanych algorytmów. 44/54 PRiR MPI operacje globalne MPI_BCAST MPI_GATHER MPI_SCATTER 45/54 PRiR 15
MPI operacje redukcji Dane początkowe MPI_REDUCE; operator MPI_MIN; root= 0 MPI_ALLREDUCE; operator MPI_MIN MPI_REDUCE; operator MPI_SUM; root= 1 46/54 PRiR MPI operacje redukcji Operatory dopuszczalne w operacjach redukcji: Maksimum i minimum MPI_MAX, MPI_MIN Suma i iloczyn MPI_SUM, MPI_PROD Logiczne and, or, xor MPI_LAND, MPI_LOR, MPI_LXOR Bitowe and, or, xor MPI_BAND, MPI_BOR,MPI_BXOR 47/54 PRiR MPI komunikatory Globalny komunikator: MPI_COMM_WORLD Tworzenie i obsługę nowego komunikatora umożliwiają 4 funkcje: MPI_COMM_DUP duplikacja (tworzenie kopii) istniejącego komunikatora MPI_COMM_SPLIT tworzenie nowego komunikatora, procesy wchodzące w skład komunikatora mogą się komunikować między sobą bez interferencji z procesami spoza komunikatora MPI_INTERCOMM_CREATE tworzenie interkomunikatora (intercommunicator) przez podział komunikatora na dwie grupy MPI_COMM_FREE- zwolnienie (zamkniecie) komunikatora 48/54 PRiR 16
MPI komunikatory Największe zastosowanie ma MPI_COMM_SPLIT: MPI_COMM_SPLIT(comm, color, key, newcomm) comm stary komunikator; poprzez jego podział stworzymy nowy color nazwa (kolor) nowego komunikatora key id procesu newcomm nowy komunikator Uwaga: jeżeli newcomm o odpowiednim kolorze nie istnieje, komunikator jest tworzony i proces jest dodawany; jeżeli istnieje, tylko dodajemy proces 49/54 PRiR MPI komunikatory MPI_Comm comm, newcomm; int myid, color; MPI_Comm_rank(comm, &myid); color = myid%3; MPI_Comm_split(comm, color, myid, &newcomm); Stworzenie 3 nowych komunikatorów jak na rysunku 50/54 PRiR MPI komunikatory Komunikacja między komunikatorami. Komunikator stworzony przez MPI_COMM_SPLIT (także MPI_COMM_WORLD) może być używany do wymiany danych pomiędzy stworzona grupą jest to parametr w operacjach przesyłania danych. Można też tworzyć interkomunikatory pomiędzy grupami i wtedy można przekazywać dane pomiędzy wszystkimi elementami pierwszej i drugiej grupy. MPI_INTERCOMM_CREATE(comm, local_leader, peercomm, remote_leader, tag, intercomm) 51/54 PRiR 17
MPI komunikatory Przykład: Podzielimy 8 procesów na dwie grupy (parzyste, nieparzyste), następnie stworzymy lokalny interkomunikatori przy jego pomocy przekażemy dane. 52/54 PRiR MPI komunikatory Przykład (cd): int comm, intercomm, myid, newid; MPI_COMM_SIZE (MPI_COMM_WORLD,&count); /* zakładamy parzysta ilosc procesow i BRAK bledow komunikacyjnych*/ MPI_COMM_RANK (MPI_COMM_WORLD, &myid); MPI_COMM_SPLIT (MPI_COMM_WORLD, myid%2,myid,&comm); MPI_COMM_RANK (comm, %newid); if(newid%2==0) // nadawca MPI_INTERCOMM_CREATE (comm, 0,MPI_COMM_WORLD, 1,99, &intercom); /* stworzenie nowego interkomunikatora */ MPI_SEND(msg,1,type,newid,0,99, intercom;) /* wysylamy 1 informacje z bufora msg typu type do odbiorcy w nowym interkomunikatorze */ Else //odbiorca MPI_INTERCOMM_CREATE (comm, 0,MPI_COMM_WORLD, 1,99, &intercom); MPI_RECEIVE(msg,1,type,newid,0,99, intercom); MPI_COMM_FREE(intercom) MPI_COMM_FREE(com) 53/54 PRiR MPI + CUDA CUDA (NVIDIA scomputeunifieddevice Architecture) umożliwia wykorzystanie procesorów graficznych do obliczeń równoległych większość dzisiejszych superkomputerów wykorzystuje procesory graficzne. MPI jest wygodnym narzędziem do budowy grup procesorów i wymiany danych pomiędzy nimi także w tym zakresie. Więcej np. w pracy: An MPI-CUDA Implementation for Massively Parallel Incompressible Flow Computations on Multi-GPU Clusters Dana A. Jacobsen, Julien C. Thibault, and Inanc Senocaky 54/54 PRiR 18