16 kwietnia 2008
Wprowadzenie Co to jest MPI? Plan prezentacji: co to jest MPI? komunikatory i grupy procesów; przesyłanie komunikatów; komunikacja kolektywna; wirtualne topologie;.
Co to jest MPI? Wstęp MPI (Message Passing Interface) jest standardem interfejsu do przesyłania komunikatów w rzeczywistych i wirtualnych maszynach równoległych z pamięcią lokalną. Został opracowany przez MPI Forum grupę badaczy z USA i Europy. Z punktu widzenia programisty jest to biblioteka dla C, C++ i Fortrana służąca do wysyłania i odbierania komunikatów oraz do synchronizacji zadań, które najczęściej wykonują ten sam program. Istnieje wiele implementacji MPI na różne platformy, np. Open MPI, MPICH.
Wstęp Co to jest MPI? Wstęp Standard MPI nie podaje, jak mają być uruchamiane procesy równoległe. W standardzie MPI 1.1, który tutaj opisujemy, brakuje mechanizmów tworzenia nowych procesów. Sposób określenia jakie zasoby będą używane oraz podanie liczby procesów zależy od konkretnej implementacji. Najczęściej program uruchamia się w taki sposób: mpirun [-v] -c <liczba_kopii_procesów> <nazwa_pliku_programu> -v jest opcją związaną z podawaniem raportów na temat wykonywanych operacji
Wstęp Inicjacja biblioteki MPI: int MPI_Init(int *argc, char **argv[]) Argumentami są parametry funkcji main. Funkcja ta może zmienić te argumenty, interpretując argumenty przekazane do biblioteki MPI i pozostawiając argumenty przeznaczone dla procesu. Koniec pracy z MPI int MPI_Finalize(void) Większość funkcji z MPI zwraca MPI SUCCESS w przypadku pomyślnego wywołania funkcji.
Operacje na grupach Operacje na komunikatorach Komunikatory zewnętrzne Podstawowym pojęciem MPI jest komunikator. Najprostsza definicja to grupa procesów plus pewien kontekst. Komunikat wysłany z danym kontekstem może być tylko odebrany przy użyciu tego samego kontekstu (czyli przez dany komunikator). Kontekst przechowuje też pewne argumenty nadane przez aplikację, które mogą być w dowolnej chwili odczytywane.
Operacje na grupach Operacje na komunikatorach Komunikatory zewnętrzne Komunikatory są identyfikowane przez uchwyty o typie MPI Comm. Po uruchomieniu MPI powstaje komunikator MPI COMM WORLD, który obejmuje wszystkie procesy. Można też pobrać kontekst procesu z uchwytu MPI COMM SELF. Jeśli jakaś funkcja zwracająca komunikator zakończy się niepowodzeniem (np. jakiś komunikator nie istnieje), to zwraca niedozwolony uchwyt MPI COMM NULL.
Operacje na grupach Operacje na komunikatorach Komunikatory zewnętrzne Grupa procesów jest zbiorem procesów. Każdy proces posiada swój indeks. Indeksy są ciągłe i należą do zakresu od 0 do liczby procesów pomniejszonej o 1. Grupy są identyfikowane przez uchwyty o typie MPI Group. Istnieje jedna predefiniowana grupa o uchwycie MPI GROUP EMPTY, która nie zawiera żadnego procesu. Jeżeli jakaś funkcja MPI nie może utworzyć grupy, zwraca wtedy uchwyt MPI GROUP NULL.
Operacje na grupach Co to jest MPI? Operacje na grupach Operacje na komunikatorach Komunikatory zewnętrzne Utworzenie kopii grupy: int MPI_Comm_group(MPI_Comm comm, MPI_Group *group) Ilość procesów w istniejącej grupie: int MPI_Group_size(MPI_Group, int *size) Indeks procesu (jeżeli wywołane z procesu grupy group, w przeciwnym wypadku MPI UNDEFINED): int MPI_Group_rank(MPI_Group group, int *rank) Zwolnienie grupy, dodatkowo pod group zostaje wpisane MPI GROUP NULL: int MPI_Group_free(MPI_Group *group)
Operacje na grupach Operacje na komunikatorach Komunikatory zewnętrzne Suma mnogościowa, przecięcie i różnica to odpowiednio: int MPI_Group_union(MPI_Group group1, MPI_Group group2, MPI_Group *newgroup) int MPI_Group_intersection(MPI_Group group1, MPI_Group group2, MPI_Group *newgroup) int MPI_Group_difference(MPI_Group group1, MPI_Group group2, MPI_Group *newgroup) Kolejność w nowej grupie jest taka jak w group1, a następnie z group2. Można też stworzyć grupę z wybranych procesów: int MPI_Group_incl(MPI_Group group, int n, int *ranks, MPI_Group *newgroup)
Operacje na komunikatorach Operacje na grupach Operacje na komunikatorach Komunikatory zewnętrzne Pobranie liczby procesów w komunikatorze comm: int MPI_Comm_size(MPI_Comm comm, int *size) Funkcja pobierająca identyfikator wywołującego ją procesu to: int MPI_Comm_rank(MPI_Comm comm, int *rank) Te funkcje są równoznaczne z użyciem MPI Comm group, uzyskaniu danych za pomocą MPI Group size lub MPI Group rank i na koniec użycie MPI Group free.
Operacje na grupach Operacje na komunikatorach Komunikatory zewnętrzne Wszystkie operacje na grupach były lokalne, tak samo wymienione dotychczas operacje na komunikatorach do ich działania nie jest potrzebna żadna komunikacja między procesami. Mogą być wykonywane niezależnie w różnych procesach, niektóre procesy mogą ich też nie używać wcale. Teraz zostaną podane funkcje kolektywne, które muszą być wykonane we wszystkich procesach komunikatora comm, będącego pierwszym argumentem funkcji. int MPI_Comm_dup(MPI_Comm comm, MPI_Comm *newcomm) Tworzy kopię komunikatora, która zawiera te same procesy w tej samej kolejności, ale ma nowy kontekst. int MPI_Comm_free(MPI_Comm *comm) Zwalnia komunikator (zewnętrzny lub wewnętrzny, o tym za chwilę).
Operacje na grupach Operacje na komunikatorach Komunikatory zewnętrzne Tworzenie nowego komunikatora: int MPI_Comm_create(MPI_Comm comm, MPI_Group group, MPI_Comm *newcomm) Grupa group powinna być identyczna (mieć te same procesy w tej samej kolejności) we wszystkich wywołujących ją kolektywnie procesach i powinna być podgupą grupy komunikatora comm. Funkcja powinna być wywołana na wszystkich procesach komunikatora comm, nawet na tych, które nie należą do group. Nowo utworzony komunikator nie dziedziczy atrybutów po comm. int MPI_Comm_split(MPI_Comm comm, int color, int key, MPI_Comm *newcomm) Dzieli komunikator na tyle komunikatorów ile jest różnych wartości color. Kolejność zdefiniowana jest za pomocą key. Wartość color może być równa MPI UNDEFINED, co powoduje, że proces nie jest włączany do żadnego z komunikatorów.
Komunikatory zewnętrzne Co to jest MPI? Operacje na grupach Operacje na komunikatorach Komunikatory zewnętrzne Komunikatory zewnętrzne są używane do przesyłania komunikatów między procesami należącymi do różnych grup. Cechą takiego komunikatora jest to, że zawiera dwie rozłączne grupy procesów. Pierwsza zawierająca proces wywołujący daną funkcją jest nazywana grupą lokalną, druga grupą zdalną. Nie wszystkie funkcje mogą używać komunikatorów zewnętrznych, przeważnie nie mogą być one używane przez funkcje kolektywne. O tym, czy komunikator jest wewnętrzny czy zewnętrzny, informuje funkcja lokalna: int MPI_Comm_test_inter(MPI_Comm comm, int *result) Pod adresem result zostaje wpisane true jeśli komunikator comm jest zewnętrzny, false w przeciwnym wypadku.
Operacje na grupach Operacje na komunikatorach Komunikatory zewnętrzne Funkcje lokalne MPI Comm size, MPI Comm rank i MPI Comm group zwracają parametry lub kopię lokalnej grupy. Do uzyskania parametrów lub kopii grupy zdalnej służą funkcje lokalne MPI Comm remote size i MPI Comm remote group. Nie istnieje odpowiednik MPI Comm rank, co jest oczywiste ze względu na to, że grupa lokalna i zdalna są rozłączne. Komunikator zewnętrzny można zamienić na wewnętrzny za pomocą funkcji int MPI_Intercomm_merge(MPI_Comm intercomm, int high, MPI_Comm *newintracomm) Wartość high w jednej z grup powinny być równe true, a w drugiej false. Spowoduje to, że procesy grupy, w której podano false, będą miały niższe indeksy.
Operacje na grupach Operacje na komunikatorach Komunikatory zewnętrzne Do tworzenia komunikatorów zewnętrznych służy funkcja kolektywna int MPI_Intercomm_create(MPI_Comm local_comm, int local_leader, MPI_Comm peer_comm, int remote_leader, int tag, MPI_Comm *newintercomm) Funkcja ta jest wywoływana przez członków grupy zdalnej i lokalnej. Jako argument local comm procesy podają uchwyt komunikatora, którego grupa jest grupą lokalną dla wołającego procesu. Uchwyt do utworzonego komunikatora jest umieszczany pod adresem newintercomm. Pozostałe argumenty są używane tylko w procesie tworzenia komunikatora i nie mają wpływu na jego późniejsze własności.
Operacje na grupach Operacje na komunikatorach Komunikatory zewnętrzne Obie grupy muszą mieć wyróżnionego członka, zwanego liderem. Lider lokalnej grupy wołającego procesu jest podawany jako argument local leader, a lider drugiej grupy jako remote leader. Dodatkowo musi istnieć komunikator wewnętrzny zawierający procesy obu grup (może zawierać też inne procesy), podawany jako argument peer comm. Najczęściej podaje się tu uchwyt MPI COMM WORLD lub jego kopię. Argument tag określa etykietę nadawaną komunikatom podczas tworzenia komunikatora. Nie może ona kolidować z żadną z etykiet komunikatorów, które nie zostały jeszcze odebrane w komunikatorze peer comm. Dlatego warto jako argumentu peer comm użyć kopii komunikatora MPI COMM WORLD. Argumenty peer comm i remote leader są ważne tylko dla dwóch procesów będących liderami obu grup. W pozostałych przypadkach są ignorowane.
Co to jest MPI? Wysyłanie Odbiór Przydzielanie buforów Funkcje nieblokujące Typy pochodne i pakowanie danych Komunikacja między dwoma procesami może być realizowana kilkoma sposobami. Zasadniczo MPI rozróżnia rodzaje komunikacji ze względu na: synchronizację wzajemną: komunikacja synchroniczna nadawca czeka na gotowość odbiorcy i kończy wysyłanie jak odbiorca potwierdzi odbiór; komunikacja asynchroniczna nadawca wysyła komunikat i nie oczekuje na potwierdzenie odbioru. warunek powrotu z funkcji wysyłanie/odbieranie: komunikacja blokująca powrót z funkcji wysyłanie/odbieranie następuje po zakończeniu operacji; komunikacja nieblokująca - powrót z funkcji wysyłanie/odbieranie nastepuje natychmiast. W MPI możliwe są różne kombinacje trybów wysyłania i odbierania.
Wysyłanie Co to jest MPI? Wysyłanie Odbiór Przydzielanie buforów Funkcje nieblokujące Typy pochodne i pakowanie danych Do blokującego wysyłania komunikatów służą funkcje: int MPI_Send(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm) int MPI_BSend(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm) int MPI_SSend(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm) Wysyłają one count danych typu datatype umieszczonych pod adresem buf do procesu o identyfikatorze dest w komunikatorze comm. Jeśli komunikator jest wewnętrzny to nadawca i odbiorca muszą należeć do jego grupy, a jeśli jest zewnętrzny, to nadawca musi należeć do jednej z grup, a odbiorca do drugiej. Argument tag określa etykietę nadawaną komunikatowi. Wartość ta powinna być z zakresu od 0 do MPI TAG UB.
Wysyłanie Odbiór Przydzielanie buforów Funkcje nieblokujące Typy pochodne i pakowanie danych Możliwe wartości argumentu datatype i odpowiadające im typy języka C: Wartość argumentu datatype Typ języka C MPI CHAR signed char MPI SHORT signed short int MPI INT signed int MPI LONG signed long int MPI UNSIGNED CHAR unsigned char MPI UNSIGNED SHORT unsigned short int MPI UNSIGNED unsigned int MPI UNSIGNED LONG unsigned long int MPI FLOAT float MPI DOUBLE double MPI LONG DOUBLE long double MPI BYTE MPI PACKED
Wysyłanie Odbiór Przydzielanie buforów Funkcje nieblokujące Typy pochodne i pakowanie danych O tym czy komunikat został wysłany w sposób synchroniczny czy asynchroniczny, decydują przedrostki w nazwach funkcji. Domyślnie używane jest wysyłanie asynchroniczne (MPI Send). Dodatkowo, można realizować wysyłanie asynchroniczne w trybie buforowanym (MPI Bsend), w którym nadawcy przydzielany jest odpowiednio duży bufor na wysyłane dane i jej wywołanie poprzedzają funkcje przydziału bufora. Wysyłanie można też realizować synchronicznie (MPI Ssend). Przy implementacji sieciowej MPI, dane są przed wysłaniem przekształcane do wspólnej reprezentacji. Po odebraniu dane są przekształcane do reprezentacji lokalnego komputera. Typ MPI BYTE oznacza, że dane nie będą przekształcane. Typ MPI PACKED oznacza, że dane są przekształcane przy pakowaniu/rozpakowywaniu danych z/do bufora.
Odbiór Co to jest MPI? Wysyłanie Odbiór Przydzielanie buforów Funkcje nieblokujące Typy pochodne i pakowanie danych Do blokującego odczytu danych służy funkcja: int MPI_Recv(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status) niezależnie od trybu przesyłania komunikatu (synchronicznie czy asynchronicznie). Funkcja czeka na wysłanie przez proces o identyfikatorze source z komunikatora comm komunikatu z etykietą tag. Wartości datatype muszą być identyczne w funkcjach wysyłania i odbierania. Argument count może być większy niż w funkcji wysyłającej komunikat.
Wysyłanie Odbiór Przydzielanie buforów Funkcje nieblokujące Typy pochodne i pakowanie danych W przypadku komunikatora zewnętrznego argument source musi być identyfikatorem procesu w zdalnej grupie. Argument source może też mieć wartość MPI ANY SOURCE, oznaczającą chęć odbierania komunikatu od dowolnego nadawcy. Podobnie argument tag może mieć wartość MPI ANY TAG oznaczający odczyt komunikatów o dowolnej etykiecie. Argument status wskazuje na strukturę, która zostanie przez funkcje wypełniona. Ma ona trzy pola MPI SOURCE, MPI TAG i MPI ERROR (odpowiednio identyfikator nadawcy, etykieta i komunikat błędu). MPI ERROR będzie wpisane tylko wtedy, gdy funkcja zwróci MPI ERR IN STATUS. Możemy też poznać ilość odebranych danych: int MPI_Get_count(MPI_Status *status, MPI_Datatype datatype, int* count)
Przydzielanie buforów Co to jest MPI? Wysyłanie Odbiór Przydzielanie buforów Funkcje nieblokujące Typy pochodne i pakowanie danych Do przesyłania komunikatów w trybie buforowanym w procesach wysyłających komunikat musi zostać utworzony bufor. Służy do tego funkcja: int MPI_Buffer_attach(void *buf, int size) Argument buf powinien wskazywać na obszar pamięci o rozmiarze przynajmniej size bajtów. Biblioteka będzie używać go przy przesyłaniu komunikatów w trybie buforowanym. Aby odzyskać pamięć przydzieloną na bufory, powinniśmy użyć funkcji: int MPI_Buffer_detach(void **buff, int *size) która dodatkowo pod adres buff wpisuje adres poprzedniego bufora, a pod adres size jego rozmiar.
Funkcje nieblokujące Co to jest MPI? Wysyłanie Odbiór Przydzielanie buforów Funkcje nieblokujące Typy pochodne i pakowanie danych Funkcje wysyłające i odbierające dane w sposób nieblokujący różnią się od wersji blokujących dodatkowym przedrostkiem I (np. MPI Isend, MPI Irecv) i posiadają jeden dodatkowy parametr MPI Request *request. Można czekać na zakończenie operacji wywołując funkcję int MPI_Wait(MPI_Request *request, MPI_Status *status) Dodatkowo pod adresem status wpisuje status zakończonej operacji. Przy wyjściu funkcja wpisuje pod adresem request wartość MPI REQUEST NULL.
Wysyłanie Odbiór Przydzielanie buforów Funkcje nieblokujące Typy pochodne i pakowanie danych Jeżeli chcemy tylko sprawdzić czy operacja się zakończyła możemy użyć: int MPI_Test(MPI_Request *request, int *flag, MPI_Status *status) Pod adresem flag zostanie zapisana wartość true jeżeli operacja się już skończyła lub false w przeciwnym przypadku. Jeżeli funkcje MPI Wait i MPI Test sygnalizują zakończenie operacji, zasoby zajmowane przez operację są zwalniane. Jeżeli operacja się nie skończyła, do zwolnienia zasobów można użyć funkcji. int MPI_Request_free(MPI_Request *request)
Wysyłanie Odbiór Przydzielanie buforów Funkcje nieblokujące Typy pochodne i pakowanie danych W niektórych przypadkach bardzo użyteczna jest funkcja: int MPI_Waitany(int count, MPI_Request requests[], int *index, MPI_Status *status) czekająca na zakończenie jakiejkolwiek z count operacji o uchwytach przekazanych w tablicy requests. Indeks zakończonej operacji i jej status są zapisywane odpowiednio pod adresami index i status. Pod indeksem zakończonej operacji w requests zostanie wpisane MPI REQUEST NULL.
Wysyłanie Odbiór Przydzielanie buforów Funkcje nieblokujące Typy pochodne i pakowanie danych Operację nieblokującą można anulować, służy do tego funkcja int MPI_Cancel(MPI_Request *request) Anulowanie operacji nie jest równoznaczne ze zwolnieniem przez nią wszystkich zasobów. Zwykle należy wywołać jeszcze funkcje MPI Request free.
Wysyłanie Odbiór Przydzielanie buforów Funkcje nieblokujące Typy pochodne i pakowanie danych W niektórych przypadkach (np. przy przesyłaniu tekstu) przed odebraniem komunikatu nie jest znana jego długość. Nie wiadomo wówczas, jaka powinna być wartość argumentu count, przekazywanego do funkcji odbierającej komunikat i jak duży powinien być bufor na dane. Informacje o długości można uzyskać wywołując funkcję: int MPI_Probe(int source, int tag, MPI_Comm comm, MPI_Status *status) Działa ona podobnie jak funkcja MPI Recv, z tym, że nie pobiera komunikatu, a jedynie jego parametry. Komunikat zostaje i po alokacji buforów może być pobrany bez czekania za pomocą funkcji MPI Recv. Istnieje też nieblokująca wersja funkcji MPI Probe MPI Iprobe, która działa jak MPI Irecv, z tym, że nie pobiera komunikatu, a jedynie informacje o nim.
Typy pochodne i pakowanie danych Wysyłanie Odbiór Przydzielanie buforów Funkcje nieblokujące Typy pochodne i pakowanie danych Opisane dotychczas operacje umożliwiały przesyłanie komunikatu zawierającego wektor danych jednakowego typu, umieszczony w ciągłym obszarze pamięci. W pozostałych przypadkach należy uzyć typów pochodnych lub pakowania danych. Użycie typów pochodnych polega na zdefiniowaniu za pomocą specjalnych funkcji nowego typu, a następnie użycie go jako argumentu datatype w odpowiednich funkcjach. Raz zdefiniowany typ można używać wielokrotnie do przesyłania różnych danych lub do definiowania kolejnych typów pochodnych. Typ będący tablicą innych typów definiuje się za pomocą funkcji: int MPI_Type_contiguous(int cout, MPI_Datatype oldtype, MPI_Datatype *newtype)
Wysyłanie Odbiór Przydzielanie buforów Funkcje nieblokujące Typy pochodne i pakowanie danych Ogólniejszą wersją tej funkcji jest funkcja: int MPI_Type_vector(int count, int blocklength, int stride, MPI_Datatype oldtype, MPI_Datatype *newtype) Definiuje ona typ składający się z count bloków zawierających blocklength elementów typu oldtype każdy. Między początkami bloków występują odstępy o długości stride elementów oldtype. Do funkcji MPI Type vector podobna jest funkcja MPI Type hvector. Jedyną różnicą jest to, iż argument stride określa przesunięcia w bajtach, a nie w długościach elementu oldtype.
Wysyłanie Odbiór Przydzielanie buforów Funkcje nieblokujące Typy pochodne i pakowanie danych Najogólniejszą z funkcji definiujących typ jest funkcja: int MPI_Type_struct(int count, int *array_of_blocklengths, MPI_Aint *array_of_displacements, MPI_Datatype *array_of_types, MPI_Datatype *newtype) Definiuje ona typ składający się z count bloków, każdy o innym typie i długości (wyrażone liczbą elementów składowych). Elementy tablicy array of displacements określają przesunięcia poszczególnych bloków względem wspólnego początku.
Wysyłanie Odbiór Przydzielanie buforów Funkcje nieblokujące Typy pochodne i pakowanie danych Po zdefiniowaniu nowego typu należy go skompilować za pomocą funkcji: int MPI_Type_commit(MPI_Datatype *type) Kompilacja jest wymagana przed użyciem go przy wysyłaniu lub odbieraniu komunikatu, nie jest jednak wymagana podczas definiowania kolejnych typów pochodnych. Po zakończeniu używania typu należy zwolnić wykorzystywane przez niego zasoby, wywołując funkcje: int MPI_Type_free(MPI_Datatype *type) Funkcja ta nie wpływa na jeszcze aktywne operacje, jak również nie niszczy typów pochodnych.
Wysyłanie Odbiór Przydzielanie buforów Funkcje nieblokujące Typy pochodne i pakowanie danych Typy pochodne są konstrukcją statyczną i nie mogą się zmieniać w zależności od przesyłanych danych. Przykładem struktur danych, których nie można w ten sposób zdefiniować są unie o wariancie określonym przez przesyłane dane albo listy. Do przesyłania bardziej złożonych konstrukcji wykorzystuje się pakowanie. Pakowanie polega na umieszczeniu danych w pomocniczym buforze, a następnie wywołaniu funkcji wysyłającej komunikat.
Wysyłanie Odbiór Przydzielanie buforów Funkcje nieblokujące Typy pochodne i pakowanie danych Do pakowania służy funkcja: int MPI_Pack(void *inbuf, int incount, MPI_Datatype datatype, void *outbuf, int outsize, int *outposition, MPI_Comm comm) Kopiuje ona incount danych typu datatype (może to być typ prosty lub pochodny) umieszczonych pod adresem inbuf do bufora o początku outbuff i rozmiarze outsize. Ponieważ w buforze mogą się już znajdować dane, zmienna wskazywana przez outposition powinna określać ile bajtów tego bufora jest już używanych. Po przekopiowaniu danych do bufora funkcja modyfikuje tę zmienną.
Wysyłanie Odbiór Przydzielanie buforów Funkcje nieblokujące Typy pochodne i pakowanie danych Przed wykonaniem pakowania wymagany rozmiar bufora nie jest z góry znany. Można go poznać wywołując funkcję: int MPI_Pack_size(int incount, MPI_Datatype datatype, MPI_Comm comm, int *size) Pod adresem size umieści ona rozmiar bufora wymagany do spakowania incount danych typu datatype. Po spakowaniu danych można je wysłać, a następnie odebrać jako typ MPI PACKED. Należy podać liczbę danych równą rozmiarowi bufora.
Wysyłanie Odbiór Przydzielanie buforów Funkcje nieblokujące Typy pochodne i pakowanie danych Przy odbiorze spakowanego komunikatu zazwyczaj nie znamy jego długości informacje o tym podaje funkcja MPI Probe. Odebrany komunikat trzeba rozpakować. Używa się do tego funkcji: int MPI_Unpack(void *inbuff, int insize, int *position, void *outbuf, int outcount, MPI_Datatype datatype, MPI_Comm comm) Pobiera ona outcount danych typu datatype i umieszcza je pod adresem outbuf. Dane są pobierane z bufora o początku inbuf i rozmiarze insize. Część danych z bufora inbuf została już wcześniej pobrana. Liczbę tę określa zmienna wskazywana przez position. Po wykonaniu funkcji zostanie ona zmodyfikowana, by uwzględnić właśnie odczytane dane. Pakowanie umożliwia przesyłanie bardziej złożonych struktur bez definiowania nowych typów. Wadą tego rozwiązania jest to, że dane muszą zostać przekopiowane do bufora podczas wysyłania i kopiowane z bufora podczas odbioru.
Co to jest MPI? Synchronizacja za pomocą bariery Wysyłanie komunikatu do grupy procesów Zbieranie danych od grupy procesów Rozsyłanie danych między członków grupy procesów Operacje redukcji Wirtualne topologie Opisane wcześniej funkcje przesyłały komunikat od jednego procesu do drugiego procesu. Zarówno nadawcą jak i odbiorcą komunikatu był dokładnie jeden proces. Za pomocą MPI można przesyłać komunikaty między więcej niż dwoma procesami. Dostępne są następujące mechanizmy: synchronizacja za pomocą bariery; wysyłanie komunikatów do grupy procesów; zbieranie danych od grupy procesów; rozsyłanie danych między członków grupy procesów; operacje redukcji.
Synchronizacja za pomocą bariery Wysyłanie komunikatu do grupy procesów Zbieranie danych od grupy procesów Rozsyłanie danych między członków grupy procesów Operacje redukcji Wirtualne topologie We wszystkich tych przypadkach komunikacja zachodzi między wyróżnionym procesem grupy (zwanym dalej liderem), a pozostałymi członkami grupy. Grupę określa się podając jej komunikator (musi być to komunikator wewnętrzny). W celu uniknięcia kolizji z komunikatami, które nie zostały jeszcze odebrane, opisane za chwilę funkcje tworzą kopię podanego komunikatora i jej używają. Wszystkie omawiane mechanizmy wymagają kolektywnej współpracy wszystkich procesów w grupie.
Synchronizacja za pomocą bariery Synchronizacja za pomocą bariery Wysyłanie komunikatu do grupy procesów Zbieranie danych od grupy procesów Rozsyłanie danych między członków grupy procesów Operacje redukcji Wirtualne topologie int MPI_Barrier(MPI_Comm comm) Funkcja realizuje operację bariery. Każdy z wywołujących ją procesów oczekuje, aż wywołają ją wszystkie procesy w grupie komunikatora comm i wtedy funkcja ta wszędzie skończy działanie.
Wysyłanie komunikatu do grupy procesów Synchronizacja za pomocą bariery Wysyłanie komunikatu do grupy procesów Zbieranie danych od grupy procesów Rozsyłanie danych między członków grupy procesów Operacje redukcji Wirtualne topologie int MPI_Bcast(void *buffer, int count, MPI_Datatype, int root, MPI_Comm comm) Funkcja realizuje rozsyłanie komunikatu od procesu root do pozostałych członków grupy komunikatora comm. Funkcja wysyła dane w procesie root, a w pozostałych procesach odbiera je. Rozsyłanych jest count danych typu datatype umieszczonych pod adresem buffer w procesie root. W pozostałych procesach grupy argument buffer pokazuje miejsce, gdzie odebrane dane zostaną umieszczone.
Zbieranie danych od grupy procesów Synchronizacja za pomocą bariery Wysyłanie komunikatu do grupy procesów Zbieranie danych od grupy procesów Rozsyłanie danych między członków grupy procesów Operacje redukcji Wirtualne topologie Operację zbierania danych od grupy procesów realizuje funkcja : iny MPI_Gather(void *sendbuf, int sendcount, MPI_Datatype sendtype, void *recvbuf, int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm) Każdy proces komunikatora (łącznie z liderem) wysyła identyczną liczbę danych sendcount umieszczonych w buforze sendbuf. Proces lidera o identyfikatorze root odbiera od każdego z nich recvcount danych typu recvtype, łączy je w kolejności identyfikatorów nadawców i umieszcza pod adresem recvbuf. Argumenty recvcount, recvbuf i recvtype są ważne tylko dla procesu o identyfikatorze root.
Synchronizacja za pomocą bariery Wysyłanie komunikatu do grupy procesów Zbieranie danych od grupy procesów Rozsyłanie danych między członków grupy procesów Operacje redukcji Wirtualne topologie Ogólniejszym wariantem funkcji MPI Gather jest funkcja: int MPI_Gatherv(void *sendbuf, int sendcount, MPI_Datatype sendtype, void *recvbuf, int recvcounts, int *displs, MPI_Datatype recvtype, int root, MPI_Comm comm) Umożliwia ona każdemu procesowi z grupy przesłanie innej liczby danych. Typy muszą być takie same. Proces root w wektorze recvcounts podaje liczbę danych wysłanych przez poszczególne procesy, a w displs miejsca gdzie należy umieścić dane od poszczególnych procesów (przesunięcia względem adresu recvbuf wyrażone rozmiarami typu recvtype). Istnieją też funkcje MPI Allgather i MPI Allgatherv różniące się od MPI Gather i MPI Gatherv tym, iż wszystkie procesy, a nie tylko lider, otrzymują wynikowy wektor. Funkcje te nie mają argumentu root.
Synchronizacja za pomocą bariery Wysyłanie komunikatu do grupy procesów Zbieranie danych od grupy procesów Rozsyłanie danych między członków grupy procesów Operacje redukcji Wirtualne topologie Rozsyłanie danych między członków grupy procesów Operację odwrotną do MPI Gather realizuje funkcja: int MPI_Scatter(void *sendbuf, int sendcount, MPI_Datatype sendtype, void *recvbuf, int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm) Po wywołaniu jej przez wszystkie procesy komunikatora comm, proces root dzieli wektor o adresie sendbuf na podwektory o rozmiarze sendcount i wysyła je do poszczególnych procesów w grupie (także do siebie). Każdy z procesów odbiera swój kawałek o rozmiarze recvcount elementów typu recvtype i umieszcza go pod adresem recvbuf. Rozmiary wszystkich kawałków muszą być identyczne.
Synchronizacja za pomocą bariery Wysyłanie komunikatu do grupy procesów Zbieranie danych od grupy procesów Rozsyłanie danych między członków grupy procesów Operacje redukcji Wirtualne topologie Również funkcja MPI Scatter ma odpowiednik, umożliwiający rozsyłanie podwektorów o różnych długościach. Realizuje to funkcja: int MPI_Scaterv(void *senbuf, int *sendcounts, int *displs, MPI_Datatype sendtype, void *recvbuf, int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm) Zakłada się w niej, że liczba elementów sendtype, rozsyłanych do poszczególnych procesów, jest umieszczona w tablicy sendcounts oraz, że tablica displs zawiera położenia poszczególnych kawałków względem adresu sendbuf wyrażone w długościach typu sendtype.
Operacje redukcji Co to jest MPI? Synchronizacja za pomocą bariery Wysyłanie komunikatu do grupy procesów Zbieranie danych od grupy procesów Rozsyłanie danych między członków grupy procesów Operacje redukcji Wirtualne topologie Operację redukcji wykonuje funkcja: int MPI_Reduce(void *sendbuf, void *recvbuf, int count, MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm) Każdy z procesów przekazuje funkcji count danych typu datatype w buforze sendbuf. Na tych danych jest wykonywana operacja określona przez argument op. Wyniki są umieszczane w buforze recvbuf procesu root o długości count elementów typu datatype. Operacje wykonywane są niezależnie dla każdej danej w buforze.
Synchronizacja za pomocą bariery Wysyłanie komunikatu do grupy procesów Zbieranie danych od grupy procesów Rozsyłanie danych między członków grupy procesów Operacje redukcji Wirtualne topologie Przykładowo, jeśli typem operacji jest maksimum, to i-tą daną bufora recvbuf będzie maksimum po wszystkich procesach z i-tej danej buforów sendbuf. Typ danych datatype musi pasować do operacji op. Dozwolone wartości argumentu op i ich znaczenie podano w tabeli na kolejnych slajdach. Operacje MPI MAXLOC i MPI MINLOC oczekują elementów będących parami, zawierającymi porównywalną daną i indeks. Wynikiem operacji na sekwencji takich par jest para zawierająca wartość maksymalną (lub minimalną) i indeks odpowiadający pierwszej takiej wartości w sekwencji.
Synchronizacja za pomocą bariery Wysyłanie komunikatu do grupy procesów Zbieranie danych od grupy procesów Rozsyłanie danych między członków grupy procesów Operacje redukcji Wirtualne topologie Wartość argumentu Dozwolony typ Znaczenie op MPI INT, MPI SHORT, MPI LONG, MPI UNSIGNED, MPI MAX MPI UNSIGNED SHORT, MPI UNSIGNED LONG, Maksimum MPI FLOAT, MPI DOUBLE, MPI LONG DOUBLE MPI INT, MPI SHORT, MPI LONG, MPI UNSIGNED, MPI MIN MPI UNSIGNED SHORT, MPI UNSIGNED LONG, Minimum MPI FLOAT, MPI DOUBLE, MPI LONG DOUBLE MPI INT, MPI SHORT, MPI LONG, MPI UNSIGNED, MPI SUM MPI UNSIGNED SHORT, MPI UNSIGNED LONG, Suma MPI FLOAT, MPI DOUBLE, MPI LONG DOUBLE MPI INT, MPI SHORT, MPI LONG, MPI UNSIGNED, MPI PROD MPI UNSIGNED SHORT, MPI UNSIGNED LONG, Iloczyn MPI FLOAT, MPI DOUBLE, MPI LONG DOUBLE
Synchronizacja za pomocą bariery Wysyłanie komunikatu do grupy procesów Zbieranie danych od grupy procesów Rozsyłanie danych między członków grupy procesów Operacje redukcji Wirtualne topologie Wartość argumentu Dozwolony typ Znaczenie op MPI LAND MPI INT, MPI SHORT, MPI LONG, MPI UNSIGNED, Logiczne AND MPI UNSIGNED SHORT, MPI UNSIGNED LONG, MPI INT, MPI SHORT, MPI LONG, MPI UNSIGNED, Binarne AND MPI BAND MPI UNSIGNED SHORT, MPI UNSIGNED LONG, (po bitach) MPI BYTE MPI LOR MPI INT, MPI SHORT, MPI LONG, MPI UNSIGNED, Logiczne OR MPI UNSIGNED SHORT, MPI UNSIGNED LONG, MPI INT, MPI SHORT, MPI LONG, MPI UNSIGNED, Binarne OR MPI BOR MPI UNSIGNED SHORT, MPI UNSIGNED LONG, (po bitach) MPI BYTE MPI LXOR MPI INT, MPI SHORT, MPI LONG, MPI UNSIGNED, Logiczne XOR MPI UNSIGNED SHORT, MPI UNSIGNED LONG, MPI INT, MPI SHORT, MPI LONG, MPI UNSIGNED, Binarne XOR MPI BXOR MPI UNSIGNED SHORT, MPI UNSIGNED LONG, (po bitach) MPI BYTE
Synchronizacja za pomocą bariery Wysyłanie komunikatu do grupy procesów Zbieranie danych od grupy procesów Rozsyłanie danych między członków grupy procesów Operacje redukcji Wirtualne topologie Wartość argumentu Dozwolony typ Znaczenie op MPI INT, MPI DOUBLE INT Maksimum MPI MAXLOC MPI LONG DOUBLE INT, INT, razem z pozycją MPI SHORT INT, MPI LONG INT MPI INT, MPI DOUBLE INT Minimum MPI MINLOC MPI LONG DOUBLE INT, INT, razem z pozycją MPI SHORT INT, MPI LONG INT
Synchronizacja za pomocą bariery Wysyłanie komunikatu do grupy procesów Zbieranie danych od grupy procesów Rozsyłanie danych między członków grupy procesów Operacje redukcji Wirtualne topologie Operacje mogą być także zdefiniowane przez użytkownika, służy do tego funkcja: int MPI_Op_create(MPI_User_function *function, int commute, MPI_Op *op) Argument commute określa czy funkcja jest przemienna (true lub false). Jeśli nie, to operacja będzie wykonywana w kolejności określonej identyfikatorami procesów. Uchwyt do zdefiniowanej operacji jest umieszczany pod adresem op. Po wykorzystaniu zdefiniowanej operacji należy zwolnić zasoby zajmowane przez definicję za pomocą funkcji: int MPI_Op_free(MPI_Op *op) Funkcja ta dodatkowo pod adresem op wpisze wartość MPI OP NULL.
Synchronizacja za pomocą bariery Wysyłanie komunikatu do grupy procesów Zbieranie danych od grupy procesów Rozsyłanie danych między członków grupy procesów Operacje redukcji Wirtualne topologie Argument function powinien wskazywać na funkcję o prototypie: void function (void* invect, void* inoutvect, int *len, MPI_Datatype *datatype) wykonującą operację na parze wektorów przekazywanych przez invect i inoutvect, liczbie elementów wskazywanej przez len, o typie umieszczonym pod adresem datatype. Wynik operacji jest umieszczony pod adresem inoutvect.
Wirtualne topologie Co to jest MPI? Synchronizacja za pomocą bariery Wysyłanie komunikatu do grupy procesów Zbieranie danych od grupy procesów Rozsyłanie danych między członków grupy procesów Operacje redukcji Wirtualne topologie Grupy procesów nadają swoim członkom indeksy od zera do liczby procesów pomniejszonej o jeden. W niektórych przypadkach nie odpowiada to topologii używanej przez algorytm. Identyfikowanie procesów za pomocą kolejnych numerów nie jest zbyt wygodnie przy obliczeniach numerycznych związanych zwykle z siatkami liczb w R n. Wiele algorytmów tak traktuje procesy, jakby były one umieszczone w węzłach siatki wielowymiarowej. Bardziej ogólnie, topologia algorytmu może być wyrażona przez graf. Węzły grafu odpowiadają procesom, a łuki drogom komunikacji. W MPI można korzystać z programów i adresowania w przestrzeni wirtualnej, którą sami określimy.
Synchronizacja za pomocą bariery Wysyłanie komunikatu do grupy procesów Zbieranie danych od grupy procesów Rozsyłanie danych między członków grupy procesów Operacje redukcji Wirtualne topologie Topologia jest przyporządkowywana komunikatorom. Funkcje tworzące nowe topologie wśród argumentów oczekują komunikatora. Tworzą one nowy komunikator o takiej samej grupie procesów i związują z nim daną topologię. Utworzony komunikator zwracają. Funkcje tworzące topologie są kolektywne dla wszystkich procesów w grupie.
Synchronizacja za pomocą bariery Wysyłanie komunikatu do grupy procesów Zbieranie danych od grupy procesów Rozsyłanie danych między członków grupy procesów Operacje redukcji Wirtualne topologie Najprostszą topologią algorytmu jest topologia kartezjańska. Tworzy ją funkcja: int MPI_Cart_create(MPI_Comm oldcomm, int ndims, int *dims, int *periods, int reorder, MPI_Comm *comm_chart) Argument oldcomm określa kopiowany komunikator. Liczbę wymiarów podaje argument ndims, a tablica dims określa rozmiary poszczególnych wymiarów. Jeśli reorder jest równe false, to indeksy poszczególnych procesów w utworzonym komunikatorze są takie same, jak w komunikatorze oldcomm, w przeciwnym razie, mogą być inne w celu dopasowania topologii wirtualnej do fizycznej topologii maszyny. Tablica periods określa czy poszczególne wymiary są cykliczne. Uchwyt do utworzonego komunikatora jest zapisywany pod adresem comm cart. Jeśli liczba procesów w komunikatorze oldcom jest większa niż wynikająca z rozmiarów dims, to utworzony komunikator ma mniej procesów, a w nadmiarowych procesach jest wpisywane MPI COMM NULL pod adresem comm cart.
Synchronizacja za pomocą bariery Wysyłanie komunikatu do grupy procesów Zbieranie danych od grupy procesów Rozsyłanie danych między członków grupy procesów Operacje redukcji Wirtualne topologie Ogólną topologię, określoną przez graf, tworzy funkcja: int MPI_Graph_create(MPI_Comm comm, int nnodes, int *index, int *edges, int reorder, MPI_Comm *comm_graph) Graf jest określony liczbą węzłów i indeksów sąsiadów każdego z węzłów. Indeksy węzłów są kolejnymi liczbami całkowitymi poczynając od zera. Liczbę węzłów określa argument nnodes. Indeksy sąsiadów kolejnych węzłów są umieszczone w tablicy edges. Tablica index zawiera sumy liczb sąsiadów danego węzła i węzłów o indeksach niższych. Pozostałe argumenty mają to samo znaczenie jak w funkcji MPI Cart create.
Synchronizacja za pomocą bariery Wysyłanie komunikatu do grupy procesów Zbieranie danych od grupy procesów Rozsyłanie danych między członków grupy procesów Operacje redukcji Wirtualne topologie Podstawową procedurą używaną podczas obliczeń w topologii kartezjańskiej jest wymiana informacji z sąsiadami wzdłuż poszczególnych współrzędnych. int MPI_Cart_shift(MPI_Comm comm, int direction, int disp, int *rank_source, int *rank_dest) Funkcja oblicza indeks procesu przesuniętego względem procesu wywołującego funkcję o disp węzłów w kierunku direction. Wyznaczony indeks jest umieszczany pod adresem rank dest. Pod adresem rank source jest wpisywany indeks procesu przesuniętego w przeciwnym kierunku. Gdy dojdzie się do brzegu, wpisywana jest wartość MPI PROC NULL.
Synchronizacja za pomocą bariery Wysyłanie komunikatu do grupy procesów Zbieranie danych od grupy procesów Rozsyłanie danych między członków grupy procesów Operacje redukcji Wirtualne topologie W przypadku topologii określonej przez graf nie istnieją współrzędne topologii, a jedynie indeksy procesów. Stąd operacją, którą często się wykorzystuje, jest pobieranie indeksów sąsiadów. Realizują to funkcje: int MPI_Graph_neighbors_count(MPI_Comm comm, int rank, int *nneighbors) int MPI_Graph_neighbors(MPI_Comm comm, int rank, int maxneighbors, int *neighbors) Pierwsza z nich pod adresem nneighbors wpisuje liczbę sąsiadów procesu o indeksie rank, a druga do wektora neighbors indeksy sąsiadów procesu o indeksie rank.
Co to jest MPI? Specyfikacja MPI 1.1 została zakończona w maju 1994r. Od tego czasu ujawniło się kilka braków w istniejącej specyfikacji. Wersja 1.2 miała za zadanie dokładniej wyjaśnić zagadnienia, pominięte w poprzedniej wersji i nie wprowadzała żadnych nowych rozszerzeń. W lipcu 1997r. powstała specyfikacja, rozszerzająca MPI m.in. o dynamiczne tworzenie procesów, komunikację jednostronną i operacje na plikach. definiuje 120 nowych funkcji (MPI 1.1 ma 124 funkcje).
Co to jest MPI? Wiele funkcji powiela się, realizując te same operacje dla różnych argumentów (np. MPI Scatter i MPI Scatterv). Tym sposobem starano się dostarczyć możliwości wykonywania pewnej operacji z bardzo złożonymi parametrami, jak i udostępniania funkcji prostszej, umożliwiającej wykonanie operacji z parametrami stosowanymi najczęściej. Wiele z funkcji zostało też wprowadzone po to, by umożliwić efektywne stosowanie pewnych mechanizmów sprzętowych. Wśród zalet możemy wymienić efektywność szczególnie na maszynach równoległych z pamięcią lokalną. Wadą jest brak heterogeniczności. Nie można łączyć różnych implementacji, a producenci nie są zainteresowani takim łączeniem. Ponadto nie można łączyć ze sobą aplikacji pisanych w różnych językach programowania.
Dziękujemy za uwagę! Referat powstał na podstawie: A. Korbowski, E Niewiadomska-Szynkieicz, Obliczenia równoległe i rozproszone, Oficyna Wydawnicza Politechniki Warszawskiej 2001;