Wprowadzenie do MPI Interdyscyplinarne Centrum Modelowania Matematycznego i Komputerowego Uniwersytet Warszawski http://www.icm.edu.pl Maciej Cytowski m.cytowski@icm.edu.pl Maciej Szpindler m.szpindler@icm.edu.pl 1
Plan Wstęp Message Passing Interface podstawy Quickstart Przegląd funkcjonalności MPI, kompilacja, uruchamianie zadań Komunikacja point-to-point Hands-on: ping-pong Komunikacja nieblokująca Typy danych MPI Konstrukcja wirtualnych topologii w MPI Komunikacja grupowa Zadanie: równoległe rozwiązanie równania Poissona 2
Wstęp Programowanie równoległe Od superkomputerów do laptopów Trudne problemy i apetyt na moc obliczeniową Współczesne trendy rozwoju procesorów Różne klasy problemów różne modele programowania równoległego Różne architektury komputerowe - różne modele programowania równoległego 3
Wstęp: Programowanie równoległe Programowanie równoległe Podstawowa założenie: program lub jego części mogą wykonywać się jednocześnie na więcej niż jednej jednostce obliczeniowej Jednostka obliczeniowa: procesor, rdzeń procesora, wątek sprzętowy, wątek systemowy, inne Różne konstrukcje programów równoległych Równoległość na poziomie algorytmu Równoległość ze względu na dane Równoległość pętli / zadań Automatyczna (systemowa/sprzętowa) równoległość 4
Wstęp: od superkomputerów do laptopów Pierwsze komputery równoległe Superkomputery MPP (Cray, Thinking Machines) Serwery SMP (SGI) Ograniczenia w przyśpieszaniu procesorów skalarnych Wprowadzenie równoległości na poziomie procesora (wielo-rdzeniowość) Dziś można mieć w laptopie procesor z 4- lub więcej rdzeniami Karty graficzne mogą mieć setki rdzeni obliczeniowych 5
Wstęp: problemy i apetyt na moc obliczeniową Złożone problemy od dziesięcioleci napędzają zapotrzebowanie na wciąż szybsze komputery Symulacje klimatu i atmosfery Dynamika białek Fizyka wysokich energii Przeszukiwanie olbrzymich zbiorów danych Google seti@home itp. Grafika wysokiej jakości Metoda śledzenia promieni w czasie rzeczywistym 6
Wstęp: współczesne trendy rozwoju procesorów Wyścig w ilości rdzeni na jeden procesor Kompromis pomiędzy liczbą rdzeni a ich wydajnością Problemy i ograniczenia technologiczne Hierarchiczna równoległość na poziomie sprzętowym Wiele procesorów Wiele rdzeni Wiele wątków Obsługa pamięci, hierarchia i dostęp do pamięci 7
Wstęp: modele programowania Modele programowania odpowiadające problematyce obliczeń Trywialna równoległość całkowita niezależność danych Nietrywialna równoległość zależność danych SPMD: Single Program Multiple Data MPMD: Multiple Program Multiple Data Stream (pipeline) Programming Task pool 8
Wstęp: modele programowania cd. Modele programowania odpowiadające architekturze sprzętowej Współdzielona pamięć szybka komunikacja i synchronizacja; ograniczona liczna procesorów Pamięć rozproszona nieograniczona liczba procesorów; koszt komunikacji rośnie z liczbą procesorów; błędy trudne do wyśledzenia 9
Architektury równoległe i sposoby ich programowania Architektury o pamięci współdzielonej (SMP, multi-core, CMT) Wątki (POSIX threads), OpenMP, biblioteki Architektury o pamięci rozproszonej (klastry) MPI Architektury o pamięci/równoległości hierarchicznej (klastry) Wątki + MPI, dziś praktycznie MPI Architektury nietypowe Wątki, dedykowane biblioteki Architektury hybrydowe MPI + dedykowane biblioteki 10
Plan Wstęp Message Passing Interface podstawy Quickstart Przegląd funkcjonalności MPI, kompilacja, uruchamianie zadań Komunikacja point-to-point Hands-on: ping-pong Komunikacja nieblokująca Typy danych MPI Konstrukcja wirtualnych topologii w MPI Komunikacja grupowa Zadanie: równoległe rozwiązanie równania Poissona 11
MPI - Message Passing Interface - podstawy Biblioteka MPI Model równoległości w MPI Komunikator Pobieranie informacji z komunikatora Startowanie i zamykanie sesji MPI Komunikaty Podstawowe typy danych w MPI Przesyłanie wiadomości 12
Podstawy: Biblioteka MPI MPI - Message Passing Interface Biblioteka (standard) do tworzenia programów równoległych Pracuje natywnie z C,C++,Fortranem Faktyczny standard programowania w oparciu o wymianę komunikatów Wersja 1.2 (MPI-1) wprowadza około 150 funkcji Wersja 2.1 (MPI-2) rozszerzona o nową funkcjonalność, razem ponad 500 funkcji Składa się z zestawu procedur i funkcji Różne implementacje MPICH / MPICH2 - http://www-unix.mcs.anl.gov/mpi/mpich/ OpenMPI - http://www.open-mpi.org/ Intel MPI ($$$) HP MPI ($$$) 13
Podstawy: model równoległości Założenia Pamięć jest rozproszona = model pamięci rozproszonej procesy wymieniają się komunikatami Jawna równoległość Proces odpowiada fizycznej jednostce obliczeniowej Program składa się z procesów podprogramów działających na poszczególnych procesorach Stworzona dla komputerów masywnie równoległych Potrafi obsłużyć setki procesów / procesów Wiele implementacji dobrze pracuje na architekturach z pamięcią współdzieloną SPMD Single Program Multiple Data ten sam podprogram pracuje na każdym procesorze (wiele instancji tego samego programu) 14
Podstawy: Komunikator Podstawowa struktura programu równoległego używającego MPI Komunikator zbiór procesów mogących się wzajemnie komunikować W ramach komunikatora każdy proces otrzymuje unikalny identyfikator (numer) tzw. rank Informacja o komunikatorze zawarta jest w zmiennej typu MPI_COMM W czasie inicjacji programu biblioteka tworzy domyślny komunikator o nazwie MPI_COMM_WORLD zawierający wszystkie dostępne procesy Programista może definiować własne komunikatory zawierające podzbiory procesów 15
MPI: Pobieranie informacji z komunikatora Liczba (np) dostępnych procesów w ramach komunikatora MPI_Comm_size(MPI_COMM_WORLD, &np) Numer (rank) mojego procesu MPI_Comm_rank(MPI_COMM_WORLD,&rank) (Uwaga Fortran) rank = 0,..., np 1 C: int MPI_Comm_size ( MPI_Comm comm, int *np ); int MPI_Comm_rank ( MPI_Comm comm, int *rank ); Fortran: CALL MPI_COMM_SIZE ( integer comm, integer np, integer ierror ) CALL MPI_COMM_RANK ( integer comm, integer rank, integer ierror ) 16
Podstawy: Startowanie i zamykanie sesji Wywołania wszelkich funkcji i procedur biblioteki MPI muszą znajdować się pomiędzy instrukcjami: MPI_Init(&argc, &argv) Inicjuje działanie biblioteki i domyślnego komunikatora (sesję) MPI_Finalize(void) Kończy działanie sesji MPI Funkcje Init i Finalize muszą być wywołane przez wszystkie procesy w ramach sesji C: int MPI_Init ( int *argc, int **argv ); int MPI_Finalize ( ); Fortran: CALL MPI_INIT( integer ierror ) CALL MPI_FINALIZE ( integer ierror ) 17
Podstawy: Komunikaty Komunikat paczka danych przesyłana pomiędzy procesami przez łącze / sieć łącząca procesory w ramach komunikatora Paczka składa się z ustalonej liczby elementów określonego typu Aby przesłać paczkę należy zdefiniować: Identyfikator (rank) procesu wysyłającego Identyfikator (rank) procesu odbierającego Adres źródłowy i adres docelowy Typ i wielkość danych przesyłanych w paczce Komunikator 18
Podstawy: Podstawowe typy danych Typ MPI Typ języka C Typ MPI Typ Fortran MPI_CHAR signed char MPI_INTEGER INTEGER MPI_SHORT signed short int MPI_REAL REAL MPI_INT MPI_LONG MPI_UNSIGNED_CHAR signed int signed long int unsigned char MPI_DOUBLE_PRECISION MPI_COMPLEX DOUBLE PRECISION COMPLEX MPI_UNSIGNED_SHORT unsigned short int MPI_LOGICAL LOGICAL MPI_UNSIGNED unsigned int MPI_CHARACTER CHARACTER(1) MPI_UNSIGNED_LONG unsigned long int MPI_FLOAT float MPI_DOUBLE double MPI_LONG_DOUBLE long double MPI definiuje własne typy danych, które powinny być odpowiednikami typów w C Dodatkowo MPI_BYTE oraz MPI_PACKED 19
Podstawy: przesyłanie komunikatów Podstawowa metoda przesłania komunikatu pomiędzy dwoma procesami Nadawca wywołuje funkcję Send Odbiorca wywołuje funkcję Receive Poszczególne komunikaty odróżnia etykieta (tag) MPI_Send / MPI_Recv to funkcje blokujące tzn. blokują dalsze wykonanie programu do czasu zakończenia komunikacji 20
Podstawy: wysyłanie wiadomości C: int MPI_Send ( void *buf, int count, MPI_Datatype datatype, \ int dest, int tag, MPI_Comm comm ); Fortran: CALL MPI_SEND ( <type> buf, integer count, integer datatype, integer dest, integer tag, integer comm, integer ierror ) buf adres tablicy zawierającej wiadomość długości count, zawierającej elementy typu datatype dest numer (rank) procesu odbierającego w ramach komunikatora comm tag etykieta wiadomości 21
Podstawy: odbieranie wiadomości C: int MPI_Recv ( void *buf, int count, MPI_Datatype datatype, \ int source, int tag, MPI_Comm comm, \ MPI_Status *status); Fortran: CALL MPI_RECV ( <type> buf, integer count, integer datatype, integer source, integer tag, integer comm, integer status, integer ierror ) buf adres tablicy zawierającej wiadomość długości count, zawierającej elementy typu datatype source numer (rank) procesu wysyłającego w ramach komunikatora comm status zawiera informacje o komunikacji (struktura zawierająca MPI_SOURCE i MPI_TAG) Jedynie wiadomość o zgodnej etykiecie (tag) zostanie odebrana 22
Plan Wstęp Message Passing Interface podstawy Quickstart Przegląd funkcjonalności MPI, kompilacja, uruchamianie zadań Komunikacja point-to-point Hands-on: ping-pong Komunikacja nieblokująca Typy danych MPI Konstrukcja wirtualnych topologii w MPI Komunikacja grupowa Zadanie: równoległe rozwiązanie równania Poissona 23
Quickstart Zadanie: wyróżniony proces odbiera i wypisuje komunikaty wysyłane przez pozostałe procesy w ramach komunikatora p1 root pn recv & write send 24
Quickstart Pliki nagłówkowe C: #include <mpi.h> Fortran: include mpif.h 25
Quickstart Wspólna część programu C: int main(int argc, char** argv) { int p, src, dest, rank; int tag = 1; char mes[50]; MPI_Status status; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &p); MPI_Finalize(); } return 0; 26
Quickstart Część programu wykonywana przez proces wysyłający (root) C: for (src = 1; src < p; src++) { MPI_Recv(mes, 50, MPI_CHAR, src, tag, \ MPI_COMM_WORLD, &status); } printf("%s\n", mes); 27
Quickstart Częśc programu wykonywana przez pozostałe procesy C: sprintf(mes, "Halo, tu proces %d", rank); dest = 0; MPI_Send(mes, strlen(mes) + 1, MPI_CHAR, dest, tag, \ MPI_COMM_WORLD); 28
Quickstart Kompletny program quickstart.c: int main(int argc, char** argv) { int p, src, dest, rank; int tag = 1; char mes[50]; MPI_Status status; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &p); if ( rank!= 0 ) { sprintf(mes, "Halo, tu proces %d", rank); dest = 0; MPI_Send(mes, strlen(mes) + 1, MPI_CHAR, dest, tag, MPI_COMM_WORLD); } } else { } for (src = 1; src < p; src++) { MPI_Recv(mes, 50, MPI_CHAR, src, tag, MPI_COMM_WORLD, &status); printf("%s\n", mes); } MPI_Finalize(); return 0; 29
Quickstart Kompilacja <ustawienie środowiska> >> mpicc quickstart.c Uruchomienie >> mpiexec np 4./a.out 30
Quickstart Definicja zadania dla systemu kolejkowego quickstart.pbs: #!/bin/csh #PBS N quickstart #PBS l nodes=1:ppn=4 #PBS l walltime=1:00:00 #PBS q test cd <workdir> <ustawienie środowiska> mpiexec./a.out Wstawienie zadania do kolejki >> qsub quickstart.pbs 31
Quickstart Załóżmy, że zamiast odbierać komunikaty od procesów o numerach 1,,p-1 kolejno odbieramy od dowolnego procesu, który w danym momencie nadaje komunikat: C: for (src = 1; src < p; src++) { MPI_Recv(mes, 50, MPI_CHAR, MPI_ANY_SOURCE, tag, \ MPI_COMM_WORLD, &status); } printf("%s\n", mes); 32
Quickstart Wprowadź zmianę z poprzedniego slajdu i uruchom program kilkakrotnie Co można zaobserwować? Dlaczego tak się dzieje?? 33
Plan Wstęp Message Passing Interface podstawy Quickstart Przegląd funkcjonalności MPI, kompilacja, uruchamianie zadań Komunikacja point-to-point Hands-on: ping-pong Komunikacja nieblokująca Typy danych MPI Konstrukcja wirtualnych topologii w MPI Komunikacja grupowa Zadanie: równoległe rozwiązanie równania Poissona 34
Przegląd funkcjonalności MPI, kompilacja, uruchamianie zadań Kompilacja programów Uruchamianie zadań Zarządzanie procesami w MPI 35
Kompilacja programów W większości implementacji biblioteki dostępne specjalne wrappery (nakładki) kompilatora C mpicc C++ - mpicc / mpicxx Fortran mpif90 / mpif77 Zastępują wywołanie właściwego kompilatora Pozwalają ustalić odpowiednie środowisko dla kompilacji i linkowania źródeł Dla gotowych źródeł często należy jawnie podać (zmienić) nazwę wywołania kompilatora 36
Kompilacja programów cd. Binaria kompilowane z biblioteką MPI z reguły nie będą przenaszalne w szczególności pomiędzy różnymi implementacjami MPI Uwaga: rozmiary typów przy mieszanych źródłach C/Fortran mogą wymagać uzgodnienia Niektóre (starsze) implementacje MPI mogą stwarzać problemy na architekturach 64-bitowych 37
Uruchamianie zadań Procesy należy startować za pomocą specjalnego narzędzia mpiexec (MPI-2) / mpirun (MPI-1) Uwaga na ustawienie środowiska (dostęp do bibliotek) Podstawowe parametry uruchomieniowe Liczba procesorów ( -n, --np. ) dla klastrów lista węzłów ( -host, -machinefile ) Przekazanie zmiennej środowiskowej ( -x, -env ) Dodatkowe parametry mogą różnić się pomiędzy wersjami biblioteki 38
Uruchamianie zadań cd. Klastry wielo-węzłowe korzystają na ogół z systemów kolejkowych System kolejkowy kontroluje przebieg zadań uruchamianych przez mpiexec-a/mpirun-a Należy odpowiednio poinstruować system kolejkowy o wymaganych zasobach System kolejkowy sam określi niezbędne parametry dla mpiexec-a/mpirun-a Uwaga: trudno oszacować ilość pamięci potrzebnej dla programu korzystającego z MPI 39
Zarządzanie procesami w MPI Sposób w jaki biblioteka MPI (część systemowa) zarządza procesami zależy od implementacji biblioteki scheduler systemowy demon / serwer kontrolujący procesy oddzielny proces użytkownika zarządzający procesami Różne komunikatory do różnych zadań operacje wejścia wyjścia i obliczenia Procesy dedykowane do specjalnych zadań Standard MPI-2 wprowadza dynamiczne zarządzanie procesami 40
Zarządzanie procesami w MPI Ciekawa funkcjonalność (specyficzne dla OpenMPI >= 1.3) mapowanie procesów w ramach przypisania procesów do węzłów: można przypisać logiczne numery procesów (rank-i) do fizycznych procesorów (socket-ów), a nawet do fizycznych rdzeni mapowanie procesów poprzez plik rankfile (-rf) realizowane przez bibliotekę Processor Affinity, dostępną tylko w linuxie przy złożonej komunikacji międzyprocesorowej można zyskać na wydajności 41
Plan Wstęp Message Passing Interface podstawy Quickstart Przegląd funkcjonalności MPI, kompilacja, uruchamianie zadań Komunikacja point-to-point Hands-on: ping-pong Komunikacja nieblokująca Typy danych MPI Konstrukcja wirtualnych topologii w MPI Komunikacja grupowa Zadanie: równoległe rozwiązanie równania Poissona 42
Komunikacja point-to-point Najprostsza forma komunikacji Proces wysyła komunikat do innego wybranego procesu Różne tryby komunikacji point-to-point (blokującej) Standardowa - MPI_Send Synchroniczna - MPI_Ssend Asynchroniczna / buforowana - MPI_Bsend Ready - MPI_Rsend różnią się tym jak restrykcyjna ma być sychronizacja Wszystkie tryby są blokujące wysyłanie kończy się w momencie gdy użytkownik może bezpiecznie odczytać i modyfikować bufor wejściowy (tablicę zawierającą komunikat) Odbieranie MPI_Recv działa ze wszystkimi trybami wysyłania 43
Różne tryby wysyłania komunikatów Tryb wysyłania Synchroniczny MPI_Ssend Standardowy MPI_Send Buforowany MPI_Bsend Ready send MPI_Rsend Działanie Pełna synchronizacja, nie zakłada wcześniejszego wywołania MPI_Recv, zakończy wykonanie, gdy odbiorca rozpocznie odbieranie Wysyłanie blokujące, biblioteka decyduje o użyciu wewnętrznego bufora Korzysta z bufora użytkownika, nie wymaga wcześniejszego wywołanie MPI_Recv, zakończy wykonanie po umieszczeniu wiadomości w buforze Zakłada wcześniejsze wywołanie MPI_Recv 44
Różne tryby wysyłania komunikatów cd. MPI_Bsend wymaga zapewnienia przez użytkownika bufora odpowiedniej wielkości MPI_Buffer_attach tworzy bufor, MPI_Buffer_detach zwalnia bufor W przypadku przepełnienia bufora nastąpi błąd MPI_Rsend zakończy się błędem, jeżeli nie wystąpi wczesniej odpowiednie wywołanie MPI_Recv Potencjalnie niebezpieczne, nie zalecane do użycia 45
Plan Wstęp Message Passing Interface podstawy Quickstart Przegląd funkcjonalności MPI, kompilacja, uruchamianie zadań Komunikacja point-to-point Hands-on: ping-pong Komunikacja nieblokująca Typy danych MPI Konstrukcja wirtualnych topologii w MPI Komunikacja grupowa Zadanie: równoległe rozwiązanie równania Poissona 46
Hands-on: Ping-Pong Napisz program, w którym dwa procesy przesyłają sobie nawzajem komunikat, tzn. pierwszy proces wysyła liczbę A do drugiego, który po jej odebraniu odsyła ją do pierwszego. Użyj timera do pomiaru czasu przesłania każdej z wiadomości. Sprawdź jak rozmiar wiadomości wpływa na czas jej przesłania. Dodatkowo: Dla każdego rozmiaru wykonaj 64 powtórzenia. Wylicz średnią wydajność w: sekundach MB/s MPI_WTIME() zwraca liczbę podwójnej precyzji, do pomiaru czasu bloku operacji należy wywołać funkcję dwukrotnie i obliczyć różnicę 47
Plan Wstęp Message Passing Interface podstawy Quickstart Przegląd funkcjonalności MPI, kompilacja, uruchamianie zadań Komunikacja point-to-point Hands-on: ping-pong Komunikacja nieblokująca Typy danych MPI Konstrukcja wirtualnych topologii w MPI Komunikacja grupowa Zadanie: równoległe rozwiązanie równania Poissona 48
Komunikacja nieblokująca zwróć wykonanie do procesu od razu i pozwól na wykonywanie innej pracy w dalszej części proces powinien sprawdzić (test) lub poczekać (wait) na zakończenie operacji nieblokującej są to operacje wymagane operacja nieblokująca + następująca po niej od razu operacja wait = operacja blokująca stosowane w celu uniknięcia impasów (z ang. deadlocks) oraz bezczynności 49
Komunikacja nieblokująca - przykład Operacja wygładzania danych Input: tablica 1D z liczbami zmiennoprzecinkowymi Algorytm: każdemu elementowi tablicy przypisujemy średnią jego dwóch sąsiadów proces ten wykonujemy N-razy Output: tablica z wygładzonymi danymi 50
Komunikacja nieblokująca - przykład Wersja skalarna kodu: #define N 262144 #define RMAX 8192 int main(int argc, char **argv) { int k,r; int left,right; double I1[N],I2[N]; double *i_old,*i_new,*tmp; r=0; do{ for(k=0;k<n;k++) { left = (k-1+n)%n; right = (k+1)%n; i_new[k] = ( i_old[left] + i_old[right] ) / 2; } tmp=i_old; i_old=i_new; i_new=tmp; // Inicjalizacja I1 for(k=0;k<n;k++) { I1[k]=k; } i_old=i1; i_new=i2; } r++; } while(r<rmax); return (int)(i_new[0]); 51
Komunikacja nieblokująca - przykład Operacja wygładzania danych komunikacja blokująca Rozwiązanie naiwne : for(liczba iteracji) wyślij wartości brzegowe do sąsiadów; odbierz wartości brzegowe od sąsiadów; uaktualnij wszystkie elementy; Napotykamy dwa problemy: impas: każdy proces wysyła operacją blokującą, a żaden nie odbiera stan bezczynności: procesy czekają na otrzymanie halo 52
Impas (z ang. deadlock) Kod w każdym procesie MPI: MPI_Send(,right_rank, ) MPI_Recv(,left_rank, ) Rozwiązanie w naszym przykładzie: nieparzyste procesy wpierw wysyłają parzyste procesy wpierw odbierają po wykonaniu zamieniają się rolami 53
Komunikacja nieblokująca - przykład Wersja MPI blokująca kodu: #include <mpi.h> #define N 262144 #define RMAX 8192 int main(int argc, char **argv) { int k,r; int NL; int left,right; double *I1,*I2; double *i_old,*i_new,*tmp; int myproc,nprocs; int rproc,lproc; MPI_Status status; NL=N/nprocs; lproc = (myproc == 0? nprocs-1 : myproc-1); rproc = (myproc == nprocs-1? 0 : myproc+1); I1 = malloc((nl+2)*sizeof(double)); I2 = malloc((nl+2)*sizeof(double)); // Inicjalizacja I1 for(k=1;k<nl+1;k++) I1[k]=myproc*NL+k-1; i_old=i1; i_new=i2; r=0; MPI_Init(&argc, &argv); MPI_Comm_size(MPI_COMM_WORLD, &nprocs); MPI_Comm_rank(MPI_COMM_WORLD, &myproc); 54
Komunikacja nieblokująca - przykład Wersja MPI blokująca kodu c.d. do{ MPI_Finalize(); if(myproc%2) { MPI_Send(&i_old[1],1,MPI_DOUBLE,lproc,1,MPI_COMM_WORLD); MPI_Send(&i_old[NL],1,MPI_DOUBLE,rproc,2,MPI_COMM_WORLD); MPI_Recv(&i_old[NL+1],1,MPI_DOUBLE,rproc,1,MPI_COMM_WORLD,&status); MPI_Recv(&i_old[0],1,MPI_DOUBLE,lproc,2,MPI_COMM_WORLD,&status); } else { MPI_Recv(&i_old[NL+1],1,MPI_DOUBLE,rproc,1,MPI_COMM_WORLD,&status); MPI_Recv(&i_old[0],1,MPI_DOUBLE,lproc,2,MPI_COMM_WORLD,&status); MPI_Send(&i_old[1],1,MPI_DOUBLE,lproc,1,MPI_COMM_WORLD); MPI_Send(&i_old[NL],1,MPI_DOUBLE,rproc,2,MPI_COMM_WORLD); } } return (int)(i_new[0]); for(k=1;k<nl+1;k++) { left=k-1; right=k+1; i_new[k] = ( i_old[left]+i_old[right]) /2; } tmp=i_old; i_old=i_new; i_new=tmp; r++; } while(r<rmax); 55
Komunikacja nieblokująca - przykład Operacja wygładzania danych komunikacja nieblokująca Rozwiązanie: for(liczba iteracji) uaktualnij elementy brzegowe; zainicjuj wysłanie wartości brzegowych; zainicjuj odebranie halo; uaktualnij elementy nie-brzegowe; poczekaj na zakończenie wysyłania; poczekaj na zakończenie odbierania; 56
Komunikacja nieblokująca - przykład Zaimplementuj schemat nieblokujący w tym wypadku czasy wykonania są podobne wiadomości są zbyt małe aby zauważyć różnicę? 57
Komunikacja nieblokująca Komunikacja realizowana w 3 krokach: inicjalizacja nieblokującej komunikacji natychmiast zwraca wykonanie do procesu nazwa funkcji rozpoczyna się od MPI_I wykonanie pracy, obliczenia, inna komunikacja czekanie na zakończenie komunikacji nieblokującej Nieblokujące wysłanie: MPI_Isend( ) //wykonanie pracy MPI_Wait( ) Nieblokujący odbiór: MPI_Irecv( ) //wykonanie pracy MPI_Wait( ) 58
Nieblokujące wysłanie C: int MPI_Isend( void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request ); int MPI_Wait ( MPI_Request *request, MPI_Status *status ); Fortran: CALL MPI_ISEND( choice buf, integer count, integer datatype, integer dest, integer tag, integer comm, integer request, integer ierror ) CALL MPI_WAIT( integer request, integer status(mpi_status_size), integer ierror ) Uwaga: buf nie powinien być używany pomiędzy Isend a operacją wait 59
Nieblokujący odbiór C: int MPI_Irecv( void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Request *request ); int MPI_Wait ( MPI_Request *request, MPI_Status *status ); Fortran: CALL MPI_ISEND( choice buf, integer count, integer datatype, integer source, integer tag, integer comm, integer request, integer ierror ) CALL MPI_WAIT( integer request, integer status(mpi_status_size), integer ierror ) Uwaga: buf nie powinien być używany pomiędzy Irecv a operacją wait 60
Zakończenie operacji nieblokujących Oczekiwanie na zakończenie operacji nieblokujących: z użyciem blokującej funkcji MPI_Wait z użyciem pętli, w której sprawdzamy wartość flagi zwróconej z wywołania funkcji MPI_Test (flag == 1 lub.true.) C: int MPI_Wait ( MPI_Request *request, MPI_Status *status ); int MPI_Test ( MPI_Request *request, int *flag, MPI_Status *status); Fortran: CALL MPI_WAIT( integer request, integer status(mpi_status_size), integer ierror ) CALL MPI_TEST( integer request, integer flag, integer status(mpi_status_size), integer ierror) 61
Wiele komunikacji nieblokujących Kilka funkcji operujących na grupie komunikacji nieblokujących: poczekaj na zakończenie lub sprawdź status jednej wiadomości z grupy: int MPI_Waitany(int count,mpi_request array_of_requests[],int *index, MPI_Status *status ) int MPI_Testany(int count,mpi_request array_of_requests[],int *index, int *flag, MPI_Status *status ) poczekaj na zakończenie lub sprawdź status wszystkich wiadomości z grupy: int MPI_Waitall(int count,mpi_request array_of_requests[],mpi_status array_of_statuses[] ) int MPI_Testall(int count,mpi_request array_of_requests[],int *flag, MPI_Status array_of_statuses[] ) poczekaj na zakończenie lub sprawdź status tylu wiadomości ile jest możliwe: int MPI_Waitsome(int incount,mpi_request array_of_requests[],int *outcount,int array_of_indices[],mpi_status array_of_statuses[]) int MPI_Testsome(int incount,mpi_request array_of_requests[],int *outcount,int array_of_indices[],mpi_status array_of_statuses[]) 62
Plan Wstęp Message Passing Interface podstawy Quickstart Przegląd funkcjonalności MPI, kompilacja, uruchamianie zadań Komunikacja point-to-point Hands-on: ping-pong Komunikacja nieblokująca Typy danych MPI Konstrukcja wirtualnych topologii w MPI Komunikacja grupowa Zadanie: równoległe rozwiązanie równania Poissona 63
Typy danych w MPI Typy określają sposób ułożenia przesyłanych i odbieranych wiadomości w pamięci int MPI_Isend(&buf,1,MPI_FLOAT,0,tag,MPI_COMM_WORLD,&req); Rozróżniamy: typy podstawowe zdefiniowane w ramach biblioteki MPI MPI_CHAR, MPI_SHORT, MPI_INT, MPI_LONG, MPI_UNSIGNED_CHAR, MPI_UNSIGNED_SHORT, MPI_UNSIGNED, MPI_UNSIGNED_LONG, MPI_FLOAT, MPI_DOUBLE, MPI_LONG_DOUBLE, MPI_BYTE typy upakowane, specyfikowane przez MPI_PACKED typy definiowalne, np. wektory, struktury, inne 64
Typy podstawowe Typ MPI Typ języka C Typ MPI Typ Fortran MPI_CHAR signed char MPI_INTEGER INTEGER MPI_SHORT signed short int MPI_REAL REAL MPI_INT signed int MPI_DOUBLE_PRECISION DOUBLE PRECISION MPI_LONG signed long int MPI_COMPLEX COMPLEX MPI_UNSIGNED_CHAR unsigned char MPI_LOGICAL LOGICAL MPI_UNSIGNED_SHORT unsigned short int MPI_CHARACTER CHARACTER(1) MPI_UNSIGNED unsigned int MPI_BYTE MPI_UNSIGNED_LONG unsigned long int MPI_PACKED MPI_FLOAT float MPI_DOUBLE double MPI_LONG_DOUBLE long double MPI_BYTE MPI_PACKED 65
Typy MPI Typ danych wyspecyfikowany w komendzie send powinien dokładnie odpowiadać typowi wyspecyfikowanemu w komendzie recv MPI_BYTE = 8 znaków binarnych MPI_PACK umożliwia pakowanie elementów różnych typów do jednego bufora MPI_PACK może również reprezentować dowolny typ danych Jeśli nasza wiadomość nie jest tablicą elementów określonego typu, tylko niejednorodną wiadomością zawierająca np. dwie liczby całkowite i tablicę liczb zmiennoprzecinkowych, wówczas musimy skorzystać z jednego z dwóch mechanizmów tworzenia takich wiadomości: pakowanie danych tworzenie własnych typów danych MPI 66
Pakowanie danych MPI udostępnia możliwość przesłania bufora pamięci zawierającego spakowane elementy różnych typów Przesłanie takiego elementu wykonujemy przy użyciu typu MPI_PACKED Aby wykorzystać ten mechanizm należy: Spakować dane za pomocą funkcji MPI_PACK Przesłać bufor pamięci za pomocą komendy send Odebrać bufor pamięci za pomocą komendy recv Rozpakować dane za pomocą komendy MPI_UNPACK Rozwiązanie dosyć wygodne, ale związane z kopiowaniem danych do nowego miejsca w pamięci 67
Pakowanie danych Pakowanie danych do bufora inkrementalne dodawanie elementów do bufora pakowanie do bufora o nazwie outbuf i rozmiarze outcount, począwszy od pozycji position, danych w liczbie incount pochodzących z bufora inbuf o typie datatype dodatkowo istnieje możliwość skorzystania z grup komunikatorów (np. grup odpowiadających architekturom procesorów) argument position musi zostać przekazany przez adres, gdyż zwracane jest na nim pozycja w buforze wyjściowym po dodaniu nowego elementu (w bajtach) C: int MPI_Pack ( void *inbuf, int incount, MPI_Datatype datatype, void *outbuf, int outcount, int *position, MPI_Comm comm ) Fortran: CALL MPI_PACK(choice inbuf, integer incount, integer datatype, choice outbuf, integer outsize, integer position, integer comm, integer ierror) 68
Pakowanie danych Aby przygotować bufor wyjściowy odpowiedniej wielkości możemy wpierw sprawdzić wymagany rozmiar za pomocą funkcji MPI_Pack_Size Fukncja ta zapisuje pod zmienną size ilość bajtów potrzebną do upakowania incount elementów typu datatype C: int MPI_Pack_size (int incount, MPI_Datatype datatype, MPI_Comm comm,int *size ) Fortran: CALL MPI_PACK_SIZE(integer incount, integer datatype, integer comm, integer size, integer ierror) Do rozpakowania danych z przesłanego bufora możemy użyć funkcji MPI_Unpack C: int MPI_Unpack ( void *inbuf, int insize, int *position,void *outbuf, int outcount, MPI_Datatype datatype,mpi_comm comm ) Fortran: CALL MPI_UNPACK(choice inbuf, integer insize, integer position, choice outbuf, integer outcount, integer datatype, integer comm, integer ierror) 69
Pakowanie danych - przykład int position, i, j, a[2]; char buff[1000];... MPI_Comm_rank(MPI_COMM_WORLD, &myrank); if (myrank == 0) { position = 0; MPI_Pack(&i, 1, MPI_INT, buff, 1000, &position, MPI_COMM_WORLD); MPI_Pack(&j, 1, MPI_INT, buff, 1000, &position, MPI_COMM_WORLD); MPI_Send( buff, position, MPI_PACKED, 1, 0, MPI_COMM_WORLD); } else /* RECEIVER CODE */ MPI_Recv( a, 2, MPI_INT, 0, 0, MPI_COMM_WORLD) } 70
Definiowalne typy danych MPI MPI umożliwia nam definiowanie 3 rodzajów własnych typów: typy reprezentujące ciągły fragment pamięci zawierający elementy tego samego typu MPI_Type_Contiguous typy będące wektorami elementów danego typu, które nie reprezentują ciągłego fragmentu pamięci MPI_Type_Vector typy będące strukturami zawierającymi elementy różnych typów, które niekoniecznie reprezentują ciągły fragment pamięci MPI_Type_Struct 71
Ciągłe fragmenty pamięci Najprostszy definiowalny typ danych MPI stary typ nowy typ C: int MPI_Type_contiguous(int count,mpi_datatype oldtype, MPI_Datatype *newtype); Fortran: CALL MPI_TYPE_CONTIGUOUS(integer count, integer oldtype, integer newtype, integer ierror) MPI_Send(&buffer,1,newtype, ); 72
Typ wektorowy Używane jeśli interesujące nas wektory danego typu nie reprezentują ciągłego fragmentu pamięci, np. pomiędzy nimi znajdują się elementy, które nie powinny być przesłane/odebrane blocklength = 3 elementy na blok stride= 4 (co ile występuje nowy element) count = 3 bloki C: int MPI_Type_vector(int count,int blocklength,int stride, MPI_Datatype oldtype,mpi_datatype *newtype); Fortran: CALL MPI_TYPE_VECTOR(integer count, integer blocklength, integer stride, integer oldtype, integer newtype, integer ierror) 73
Struktury MPI Używane do przesyłania struktur zawierających elementy różnego typu, które niekoniecznie reprezentują ciągły fragment pamięci MPI_INT MPI_DOUBLE Count = 2 Array_of_blocklengths = {3,3} Array_of_displacements = {0,addr1-addr0} Array_of_types = {MPI_INT,MPI_DOUBLE} addr0 blok 0 blok 1 addr1 przerwa, np. jeśli wymagane jest uliniowienie co 8 bajtów C: int MPI_Type_struct(int count,int *array_of_blocklengths, MPI_Aint *array_of_displacements,mpi_datatype *array_of_types, MPI_datatype *newtype); Fortran: CALL MPI_TYPE_STRUCT(integer count, integer array_of_blocklengths(*), integer array_of_displacements(*), integer array_of_types(*), integer newtype, integer ierror) 74
Struktury MPI Aby obliczyć przemieszczenie potrzebne są nam adresy poszczególnych bloków Możemy do tego celu użyć funkcji MPI MPI-1 C: int MPI_Address(void* location,mpi_aint *address); Fortran: CALL MPI_ADDRESS(choice location, integer address, integer ierror) MPI-2 C: int MPI_Get_address(void *location, MPI_Aint *address); Fortran: CALL MPI_GET_ADDRESS(choice location(*), integer (KIND=MPI_ADDRESS_KIND) address, integer ierror) 75
Zatwierdzenie nowego typu Zanim użyjemy nowego typu danych MPI, musimy go zatwierdzić za pomocą komendy MPI_TYPE_COMMIT Musi być to wykonane tylko raz dla każdego procesu MPI C: int MPI_Type_commit(MPI_Datatype *datatype); Fortran: CALL MPI_TYPE_COMMIT(integer datatype, integer ierror) 76
Plan Wstęp Message Passing Interface podstawy Quickstart Przegląd funkcjonalności MPI, kompilacja, uruchamianie zadań Komunikacja point-to-point Hands-on: ping-pong Komunikacja nieblokująca Typy danych MPI Konstrukcja wirtualnych topologii w MPI Komunikacja grupowa Zadanie: równoległe rozwiązanie równania Poissona 77
Wirtualne topologie Wirtualne topologie to mechanizm pozwalający nazywać procesy w komunikatorze w sposób, który lepiej pasuje do schematu komunikacji pomiędzy procesami Wirtualnych topologii użyjemy na przykład jeśli obliczenia wykonywane są na dwu-wymiarowej siatce i każdy proces komunikuje się tylko i wyłącznie ze swoimi najbliższymi sąsiadami w siatce 78
Wirtualne topologie Za mechanizmem wirtualnych topologii kryje się czysta arytmetyka liczb całkowitych Zyski ze stosowania tego mechanizmu: prostszy kod czasem lepsza wydajność podpowiadamy bibliotece jak wygląda schemat komunikacji Nowa topologia tworzy nowy komunikator MPI zapewnia funkcje mapowania indeksów: w celu obliczenia numeru procesu na podstawie nazewnictwa stosowanego w topologii w celu obliczenia współrzędnych w topologii na podstawie numeru procesu 79
Wirtualne topologie C: int MPI_Cart_create ( MPI_Comm comm_old, int ndims, int *dims, int *periods, int reorder, MPI_Comm *comm_cart ) Fortran: CALL MPI_CART_CREATE(INTEGER COMM_OLD,INTEGER NDIMS,INTEGER DIMS(*), INTEGER PERIODS(*),INTEGER REORDER,INTEGER COMM_CART,INTEGER IERROR) Utworzenie nowego komunikatora o topologii kartezjańskiej ndims wymiar topologii kartezjańskiej (1 linia, 2 torus lub siatka 2D, 3 torus lub sitaka 3D) dims tablica definiująca liczbę procesów w każdym wymiarze periods tablica definiująca czy siatka zawiera zapętlenia w każdym z wymiarów reorder czy pozwalamy na zmianę numerów procesów w nowym komunikatorze 80
Wirtualne topologie C: int MPI_Cart_coords ( MPI_Comm comm, int rank, int maxdims, int *coords ) Fortran: CALL MPI_CART_COORDS(INTEGER COMM,INTEGER RANK,INTEGER MAXDIMS, INTEGER COORDS(*),INTEGER IERROR) rank - numer procesu maxdims - rozmiar tablicy coords. Funkcja zapisuje do tablicy coords współrzędne procesu o numerze rank C: int MPI_Cart_rank( MPI_Comm comm,int *coords,int *rank ) Fortran: CALL MPI_CART_RANK(INTEGER COMM,INTEGER COORDS(*),INTEGER RANK, INTEGER IERROR) Funkcja zapisuje pod adresem rank numer procesu o współrzędnych określonych w tablicy coords. 81
Wirtualne topologie Jak znaleźć sąsiadów? C: int MPI_Cart_shift ( MPI_Comm comm, int direction, int displ, int *source, int *dest ) Fortran: CALL MPI_CART_SHIFT(INTEGER COMM,INTEGER DIRECTION,INTEGER DISP, INTEGER RANK_SOURCE,INTEGER RANK_DEST,INTEGER IERROR) Funkcja ta przesuwa numer lokalnego procesu wzdłuż wyspecyfikowanego wymiaru, w celu wygenerowania numerów procesów źródłowego i docelowego Numer źródła otrzymany jest poprzez odjęcie displ od n-tej współrzędnej lokalnego procesu, gdzie n równe jest direction. Numer docelowego procesu otrzymywany jest poprzez dodawanie disp do n-tej współrzędnej. Współrzędne numerowane są od zera. Jeśli topologia nie jest periodyczna w wybranym wymiarze, to przy wyliczeniach sąsiadów procesorów brzegowych zostanie zwrócona wartość MPI_PROC_NULL 82
Plan Wstęp Message Passing Interface podstawy Quickstart Przegląd funkcjonalności MPI, kompilacja, uruchamianie zadań Komunikacja point-to-point Hands-on: ping-pong Komunikacja nieblokująca Typy danych MPI Konstrukcja wirtualnych topologii w MPI Komunikacja grupowa Zadanie: równoległe rozwiązanie równania Poissona 83
Komunikacja grupowa Komunikacja grupowa, czyli taka, w której uczestniczy grupa procesów MPI Dana funkcja wywoływana jest przez wszystkie procesy w komunikatorze W MPI wyróżniamy następujące typy komunikacji grupowej: Bariera synchronizacyjna operacja Broadcast nadaj wiadomość wielu procesom operacja typu rozrzuć (z ang. scatter) operacja typu zbierz (z ang. gather) operacja wszyscy do wszystkich operacja redukcji (np. globalna suma, globalne maksimum,..) 84
Bariera synchronizacyjna C: int MPI_Barrier(MPI_Comm comm); Fortran: CALL MPI_BARRIER(integer comm, integer ierror) Zwykle bariera nie jest potrzebna, gdyż odpowiednia synchronizacja powinna być zapewniona na bazie interfejsu przesyłania komunikatów Może być używana do: debugowania programów równoległych profilingu programów równoległych synchronizacji do operacji I/O 85
Operacja Broadcast Operacja polegająca na rozesłaniu tej samej wiadomości do wszystkich procesów grupy C: int MPI_Bcast(void* buffer, int count, MPI_Datatype datatype, int root, MPI_Comm comm); Fortran: CALL MPI_BCAST(choice buffer, integer count, integer datatype, integer root, integer comm, integer ierror) 86
Operacja scatter Operacja polegająca na rozesłaniu przez proces root wektora sendbuf podzielonego na kawałki sendcount do procesów w grupie (również do siebie) C: int MPI_Scatter(void* sendbuf,int sendcount,mpi_datatype sendtype,void* recvbuf, int recvcount,mpi_datatype recvtype,int root,mpi_comm comm); Fortran: CALL MPI_SCATTER(choice sendbuf, integer sendcount, integer sendtype, choice recvbuf, integer recvcount, integer recvtype, integer root, integer comm, integer ierror) 87
Operacja gather Operacja odwrotna do scatter, czyli zebranie kawałków wektora z procesorów w grupie i zapisanie ich w buforze wybranego procesora C: int MPI_Gather(void* sendbuf,int sendcount,mpi_datatype sendtype, void* recvbuf,int recvcount,mpi_datatype recvtype,int root, MPI_Comm comm); Fortran: CALL MPI_GATHER(choice sendbuf, integer sendcount, integer sendtype, choice recvbuf, integer recvcount, integer recvtype, integer root, integer comm, integer ierror) 88
Wszyscy do wszystkich Każdy z procesów wysyła do procesu i-tego sendcount kolejnych elementów typu sendtype, poczynając od elementu i*sendtype tablicy sendbuf C: int MPI_Alltoall( void *sendbuf, int sendcount, MPI_Datatype sendtype, void *recvbuf, int recvcount, MPI_Datatype recvtype, MPI_Comm comm ) Fortran: CALL MPI_ALLTOALL(choice sendbuf, integer sendcount, integer sendtype, choice recvbuf, integer recvcount, integer recvtype, integer comm, integer ierror) 89
Operacja redukcji Służy do wykonywania globalnej operacji na elementach procesów należących do grupy Operacja typu: d 0 od 1 od 2 o od n-1 d i to dane w procesie o numerze i (skalar lub wektor) o to pewna zdefiniowana operacja Istnieje możliwość skorzystania z gotowych definicji operacji takich jak suma czy maksimum Istnieje możliwość zdefiniowania własnych operacji funkcją MPI_Op_create(..) 90
Operacja redukcji globalna suma Chcemy dokonać sumowania pewnej wartości zawartej w inbuf po wszystkich procesach w grupie Użyjemy operacji MPI_SUM Wynik ma zostać zwrócony na resultbuf C: int MPI_Reduce( &inbuf, &resultbuf, 1, MPI_INT, MPI_SUM, root, MPI_COMM_WORLD ); Fortran: CALL MPI_REDUCE( inbuf, resultbuf, 1, MPI_INTEGER, MPI_SUM, root, MPI_COMM_WORLD, ierror) Dostępna jest również operacja MPI_ALLREDUCE (bez parametru root), która jest połączeniem operacji Reduce i Broadcast zwraca wynik w każdym procesie 91
Przykład: równoległe obliczanie liczby Pi Całkowanie metodą prostokątów następującego wzoru: 4 1 n 1 2 1 x 0 i 0 2 1 i 0.5 1 ( ) n h 1,2 1 0,8 0,6 0,4 0,2 0 92
Przykład: równoległe obliczanie liczby Pi #include "mpi.h" #include <stdio.h> #include <math.h> int main( int argc, char *argv[] ){ int n, nl, myid, numprocs, i; double PI25DT = 3.141592653589793238462643; double mypi, pi, h, sum, x; MPI_Init(&argc,&argv); MPI_Comm_size(MPI_COMM_WORLD,&numprocs); MPI_Comm_rank(MPI_COMM_WORLD,&myid); n = 4096; nl = n/numprocs; h = 1.0 / (double) n; sum = 0.0; for (i = myid*nl; i < (myid+1)*nl; i ++) { x = h * ((double)i + 0.5); sum += (4.0 / (1.0 + x*x)); } mypi = h * sum; MPI_Reduce(&mypi, &pi, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); if (myid == 0) printf("pi wynosi %.16f\n",pi); MPI_Finalize(); return 0; } 93
Tabela zdefiniowanych operacji redukcji Nazwa operacji MPI_MAX MPI_MIN MPI_SUM MPI_PROD MPI_LAND MPI_BAND MPI_LOR MPI_BOR MPI_LXOR MPI_BXOR MPI_MAXLOC MPI_MINLOC Funkcja Maksimum Minimum Suma Iloczyn Logiczna koniunkcja Koniunkcja binarna Logiczna alternatywa Alternatywa binarna Logiczna alternatywa wykluczająca Binarna alternatywa wykluczająca Maksimum i jego pozycja Minimum i jego pozycja 94
Plan Wstęp Message Passing Interface podstawy Quickstart Przegląd funkcjonalności MPI, kompilacja, uruchamianie zadań Komunikacja point-to-point Hands-on: ping-pong Komunikacja nieblokująca Typy danych MPI Konstrukcja wirtualnych topologii w MPI Komunikacja grupowa Zadanie: równoległe rozwiązanie równania Poissona 95
Równanie Poisson a metoda różnic skończonych metoda iteracyjna Jacobi ego 5-punktowy krzyżak 96
Wymiana brzegu halo Pamięć rozproszona RAM1 CPU1 RAM2 CPU2 RAM3 CPU3 RAM4 CPU4 97
Wymiana brzegu halo Pamięć rozproszona RAM1 CPU1 RAM2 CPU2 RAM3 CPU3 RAM4 CPU4 98
Wymiana brzegu halo Pamięć rozproszona RAM1 CPU1 RAM2 CPU2 RAM3 CPU3 RAM4 CPU4 99
Dziękujemy za uwagę 100