MPI: podstawy i komunikacja punktowa. Charakterystyka standardowego interfejsu przesyłania wiadomości MPI: Kod jest napisany w „zwyczajnym” języku programowania.

Slides:



Advertisements
Podobne prezentacje
Tablice 1. Deklaracja tablicy
Advertisements

Język C/C++ Funkcje.
Mechanizmy pracy równoległej
Deklaracje i definicje klas w C++ Składowe, pola, metody Konstruktory
Wzorce.
Programowanie w języku Visual Basic
Język ANSI C Funkcje Wykład: Programowanie komputerów
Prowadzący: mgr inż. Elżbieta Majka
Obliczenia równoległe
Programowanie I Rekurencja.
Systemy rozproszone W. Bartkiewicz
Języki programowania C++
PROGRAMOWANIE STRUKTURALNE
argumenty wiersza poleceń: getopt
formatowanie kodu źródłowego
Projektowanie i implementacja programów obsługujących gniazdka Wykład II Zbigniew Brożbar Paweł Baranowski.
Materiały do zajęć z przedmiotu: Narzędzia i języki programowania Programowanie w języku PASCAL Część 7: Procedury i funkcje © Jan Kaczmarek.
Materiały do zajęć z przedmiotu: Narzędzia i języki programowania Programowanie w języku PASCAL Część 8: Wykorzystanie procedur i funkcji © Jan Kaczmarek.
Materiały do zajęć z przedmiotu: Narzędzia i języki programowania Programowanie w języku PASCAL Część 5: Typy porządkowe, wyliczeniowe i okrojone. Definiowanie.
Programowanie imperatywne i język C Copyright, 2004 © Jerzy R. Nawrocki Wprowadzenie.
Kurs Pascala – spis treści
Tablice.
Systemy operacyjne Wykład nr 4: Procesy Piotr Bilski.
1 Dygresja: cztery płyty główne…. 2 Dygresja: osobliwości C /* cos o nieistniejacym typie Boolean */ /* oraz o operatorze przecinkowym */ #include int.
Ogólne jednostki programowe 1
Wykład 1: Wskaźniki Podstawy programowania Programowanie w C
Instytut Fizyki Teoretycznej
Język ANSI C Operacje we/wy
Komunikaty sterujące zestawu protokołów TCP/IP
Komunikacja zbiorowa: część II
Instrukcja skoku GO TO etykieta Np. GO TO 100 ….. 100WRITE (*,*) Przeskok do instrukcji 100 Uwaga! NIE WOLNO skakać do wnętrzna złożonych instrukcji warunkowych.
Podstawy MPI-2: część II Dynamiczne zarządzanie procesami
Wątki.
Pamięć wspólna Przegląd stosowanych rozwiązań Marcin Kamiński, Michał Kotra Wydział EAIiE Katedra Automatyki Kraków, 2008.
Podstawy programowania
Pakiety w Javie Łukasz Smyczyński (132834). Czym są pakiety? Klasy w Javie są grupowane w pewne zbiory zwane pakietami. Pakiety są więc pewnym podzbiorem.
Podstawy programowania II
Podstawy programowania II Wykład 2: Biblioteka stdio.h Zachodniopomorska Szkoła Biznesu.
Podstawy programowania
Przełączanie OSI warstwa 2
Podstawy programowania
Programowanie w języku Matlab
Programowanie strukturalne i obiektowe
Równoległy algorytm metody Jacobiego rozwiązywania zagadanienia brzegowego dla eliptycznych równań różniczkowych cząstkowych.
Podstawy programowania. Język C i C++– podstawy Temat: 1
1 Wykład 8 Podprogramy. 2 Pojęcie i istota stosowania dzielenie programu na części (logicznie spójne) - nazwane - niezależne od pozostałych części - z.
Współczynnik przyspieszenia n - wielkość zadania p - liczba procesorów T(n,p) – czas wykonania.
Automatyka i Robotyka Systemy czasu rzeczywistego Wykład 4.
Jerzy F. Kotowski1 Informatyka I Wykład 14 DEKLARATORY.
Programowanie obiektowe – zastosowanie języka Java SE
Andrzej Repak Nr albumu
Inicjalizacja i sprzątanie
Buforowanie D e f i n i c j a.
Definiowanie typów danych użytkownika
Przekazywanie parametrów do funkcji oraz zmienne globalne i lokalne
Wykład 7 Synchronizacja procesów i wątków
Technologie internetowe Wykład 5 Wprowadzenie do skrytpów serwerowych.
Algorytmy- Wprowadzenie do programowania
Programowanie proceduralne Podstawy Programowania dla geoinformatyków Wykład 3 Rafał Witkowski, 2015.
Wprowadzenie, architektury równoległe i obliczenia równoległe, klastry
1 An Introduction to MPI Parallel Programming with the Message Passing Interface William Gropp Ewing Lusk Argonne National Laboratory Wprowdzenie do MPI.
Komunikacja zbiorowa – część I Komunikacja zbiorowa: przesyłanie danych pomiędzy wszystkimi procesorami grupy zdefiniowanymi przez dany komunikator. Typy.
Seminarium Dyplomowe: Metodyka i Techniki Programowania Autor: Bartłomiej Fornal.
Łukasz Sztangret Katedra Informatyki Stosowanej i Modelowania Prezentacja przygotowana w oparciu o materiały Danuty Szeligi i Pawła Jerzego Matuszyka Podstawy.
Podstawy informatyki Mechanizm obsługi sytuacji wyjątkowych Łukasz Sztangret Katedra Informatyki Stosowanej i Modelowania Prezentacja przygotowana w oparciu.
K URS JĘZYKA C++ – WYKŁAD 3 ( ) Przenoszenie Składowe statyczne Funkcje wbudowane Argumenty domyślne.
Programowanie obiektowe – zastosowanie języka Java SE
Haskell Składnia funkcji.
Język C++ Typy Łukasz Sztangret Katedra Informatyki Stosowanej i Modelowania Prezentacja przygotowana w oparciu o materiały Danuty Szeligi i Pawła Jerzego.
dynamiczny przydział pamięci
Zapis prezentacji:

MPI: podstawy i komunikacja punktowa

Charakterystyka standardowego interfejsu przesyłania wiadomości MPI: Kod jest napisany w „zwyczajnym” języku programowania (Fortran 77, Fortran 90, C, C++); przesyłanie wiadomości jest realizowane poprzez wywołanie odpowiednich procedur lub funkcji. Wszystkie zmienne są lokalne dla danego procesora; inny procesor może je uzyskać tylko poprzez wymianę wiadomości. Zwykle każdy procesor realizuje ten sam program wykonywalny (Single Program Multiple Data; SPMD), jednak występuje podział na procesor (procesory) nadzorujące (master) oraz „robotników” (workers) lub „niewolników” (slaves); realizują one inne fragmenty kodu, niż master. IF (ME == MASTER) THEN CALL SUB_MASTER(parametry) ELSE CALL SUB_WORKER(parametry) ENDIF W celu umożliwienia realizacji innych części kodu przez dany procesor lub grupę procesorów, każdy procesor ma własny identyfikator (0, 1,..., NPROC-1).

Konstrukcja systemu przesyłania wiadomości

Kompilacja z użyciem bibliotek MPI Najprościej: użyć odpowiedniego skryptu wywołującego kompilator z dołączaniem bibliotek MPI: mpif77 - Fortran 77 mpicc - C mpiCC - C++ Poniżej podany jest przykład linii polecenia dla kompilacji kodu źródłowego programu hello w Fortranie 77. mpif77 -o hello hello.f Makefile FC = /usr/bin/g77 INSTALL_DIR=/opt/scali FFLAGS = -c ${OPT} -I$(INSTALL_DIR)/include LIBS = -L$(INSTALL_DIR)/lib_pgi -L$(INSTALL_DIR)/lib -lmpi -lfmpi.SUFFIXES:.f.f.o: ${FC} ${FFLAGS} $*.f hello: hello.o ${FC} -o hello $(LIBS) hello.o

Pisanie kodów żródłowych z użyciem MPI - inicjalizacja, zakończenie, informacje o przydzielonych procesorach. W każdym żródle muszą się znaleźć definicje zawarte w pliku mpi.h (C) lub mpif.h (Fortran); plik ten musi być zaspecyfikowany jako pierwszy plik include. Program musi zawsze zawierać instrukcję inicjalizacji MPI (MPI_Init) i zakończenia MPI (MPI_Finalize). Komunikacja między procesorami oraz inne funkcje MPI są realizowane poprzez wywołanie odpowiednich procedur.MPI_InitMPI_Finalize Ogólna postać wywołania procedur MPI jest następująca: C: ierr = MPI_Xyyyyy( parametry ) lub MPI_Xyyyyy( parametry ) Zmienna ierr typu int jest kodem wyjścia z procedury; 0 oznacza zakończenie poprawne. Należy zwrócić uwagę, że nazwa procedury MPI zaczyna się od MPI_X, gdzie X jest pierwszą literą nazwy procedury (zawsze duża litera); dalsza część nazwy jest pisana małymi literami. Fortran (77 lub 90): CALL MPI_XYYYYY( parametry, IERROR ) Podobnie jak w wersji C, IERROR (zmienna typu INTEGER) jest kodem wyjścia. Zgodnie z konwencją Fortranu, wielkość liter w nazwie procedury nie odgrywa roli.

Przykład programu z użyciem bibliotek MPI (C): #include "mpi.h" #include int main( argc, argv ) int argc; char **argv; { int rank, size, resultlen; char nodename[31]; MPI_Init( &argc, &argv ); MPI_Comm_rank( MPI_COMM_WORLD, &rank ); MPI_Comm_size( MPI_COMM_WORLD, &size ); MPI_Get_processor_name( nodename, &resultlen ); printf( "Hello world! I'm %d of %d my name is %s\n", rank, size, nodename ); MPI_Finalize(); return 0; }

Przykład programu z użyciem bibliotek MPI (Fortran 77): program main include "mpif.h" integer rank, size, resultlen character*32 nodename call MPI_Init( ierr ) call MPI_Comm_rank( MPI_COMM_WORLD, rank, ierr ) call MPI_Comm_size( MPI_COMM_WORLD, size, ierr ) call MPI_Get_processor_name( nodename, resultlen, ierr ) print 10, rank, size, nodename call MPI_Finalize(ierr); 10 format("Hello world! I'm",i3," of",i3," my name is ",a8) return end

Wyniki etoh:~/MPI/Cw1/F77> mpirun -np 4 -nolocal hello Hello world! I'm 3 of 4 my name is mm17 Hello world! I'm 2 of 4 my name is mm16 Hello world! I'm 1 of 4 my name is mm15 Hello world! I'm 0 of 4 my name is mm14

Komunikacja punktowa: rodzaje, tryby i stosowanie Komunikacja punktowa: wymiana wiadomości pomiędzy dwoma procesami.

Definicja i konstrukcja wiadomości Wiadomość: pakiet danych przemieszczających się między procesorami. Podobnie jak list czy faks, oprócz właściwych przesyłanych danych musi ona być opakowana w „kopertę” (zawierać nagłówek) umożliwiający jej dostarczenie do właściwego odbiorcy :kopertę

KopertaKoperta musi zawierać następujące informacje dla systemu przesyłania wiadomości: Procesor wysyłający Lokalizacja źródła wiadomości Typ przesyłanych danych Długość przesyłanych danych Procesor(y) odbierające Lokalizacja odbiorcy wiadomości Wielkość buforu odbiorcy

#include "mpi.h" #include int main( argc, argv ) int argc; char **argv; { int rank, size, to, from, tag, count, i, ierr; int src, dest; int st_source, st_tag, st_count; MPI_Status status; double data[100]; MPI_Init( &argc, &argv ); MPI_Comm_rank( MPI_COMM_WORLD, &rank ); MPI_Comm_size( MPI_COMM_WORLD, &size ); printf("Process %d of %d is alive\n",rank,size); dest = size - 1; src = 0; if (rank == src) { to = dest; count = 10; tag = 2001; for (i=0;i<10;i++) { data[i] = i+1; } MPI_Send( data, count, MPI_DOUBLE_PRECISION, to,tag, MPI_COMM_WORLD ); }else if (rank == dest) { tag = MPI_ANY_TAG; count = 10; from = MPI_ANY_SOURCE; MPI_Recv( data, count, MPI_DOUBLE_PRECISION, from,tag, MPI_COMM_WORLD, &status ); printf("%d received ",rank); for (i=0;i<10;i++) printf ("%10.5f",data[i]); printf("\n"); } MPI_Finalize(); return 0; }

Proces wysyłający: źródło wysyła wiadomość do procesu przyjmującego: celu. Oba procesy muszą się znajdować w obrębie komunikatora zaspecyfikowanego w instrukcji wysyłania i przyjmowania. Proces celowy jest identyfikowany poprzez jego rząd. Wiadomości są przyjmowane w kolejności, w jakiej zostały wysłane. Charakterystyka komunikacji punktowej

Rodzaje komunikacji punktowej (dotyczą i nadawcy i odbiorcy) Komunikacja wstrzymująca (blocking communication) – powrót z funkcji wysyłanie/odbieranie następuje po zakończeniu operacji, tj. funkcja czeka na wynik swego działania. Komunikacja niewstrzymująca (nonblocking communication) – powrót z funkcji wysyłanie/odbieranie następuje natychmiast, tj. funkcja nie czeka na wynik swego działania.

Tryby komunikacji puntowej (dotyczą jedynie nadawcy) 1.Standardowy (Standard) wysyłanie nie jest zsynchronizowane z odbiorem; nie zakłada się buforowania wiadomości przez MPI. 2.Synchroniczny (Synchronous) proces wysyłający czeka aż wiadomość zostanie odebrana. 3.Buforowany (Buffered) buforowanie wiadomości jest kontrolowane przez programistę. 4.Gotowości (Ready) przy wysyłaniu zakłada się, że proces odbierający już zainicjował procedurę odbioru wiadomości.

Prototypy procedur obsługi komunikacji punktowej Podstawowe: MPI_Send – wysyłanie; MPI_Recv – odbiór. Bardziej zaawansowane ale ułatwiające życie: MPI_Wait – oczekiwanie na zakończenie wysyłania/odbioru; MPI_Test – sprawdzanie zakończenia wysyłania/odbioru; MPI_Probe – sprawdzanie rozmiaru i innych cech wiadomości bez jej odbierania. Bardzo zaawansowane, ważne przy optymalizacji komunikacji: MPI_Send_init – otwarcie „gorącej linii”; MPI_Start – rozpoczęcie przesyłania przez „gorącą linię”.

Standardowa procedura wysyłania MPI_SendMPI_Send( buf, count, datatype, dest, tag, comm ) buf - zmienna zawierająca wysyłane dane count - liczba wysyłanych danych datatype - typ wysyłanych danych (patrz niżej) dest - rząd (numer) procesu celowego tag - identyfikator („pieczątka”) wiadomości comm – komunikator C: int MPI_Send(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm) Fortran: MPI_SEND(BUF, COUNT, DATATYPE, DEST, TAG, COMM, IERROR) BUF(*) INTEGER COUNT, DATATYPE, DEST, TAG, COMM, IERROR

Standardowa procedura odbioru MPI_RecvMPI_Recv( buf, count, datatype, source, tag, comm, status ) buf - zmienna, do której przychodzą przyjmowane dane count - liczba elementów przychodzących danych datatype - typ przychodzących danych source - rząd (numer) procesora, który wysłał wiadomość tag - identyfikator wiadomości (jeżeli jest zaspecyfikowany explicite, zostanie odebrana wiadomość posiadająca ten właśnie identyfikator) comm - komunikator status - tablica o rozmiarze MPI_STATUS_SIZE zawierająca informacje o przychodzącej wiadomości

C: int MPI_Recv(void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status) Fortran: MPI_RECV(BUF, COUNT, DATATYPE, SOURCE, TAG, COMM, STATUS, IERROR) BUF(*) INTEGER COUNT, DATATYPE, SOURCE, TAG, COMM, STATUS(MPI_STATUS_SIZE), IERROR

Identyfikatory pełniące rolę „gwiazdek”: MPI_ANY_SOURCE – jakikolwiek proces; MPI_ANY_TAG – jakakolwiek pieczątka.

status(MPI_TAG) – pieczątka odbieranej wiadomości status(MPI_SOURCE) – rząd procesu wysyłającego Informacja o rozmiarze odbieranej wiadomości: MPI_GET_COUNT(status, datatype, count) datatype – typ danych zawartych w odbieranej wiadomości count – rozmiar odbieranej wiadomości C: int MPI_Get_count(MPI_Status status, MPI_Datatype datatype, int *count) Fortran: MPI_GET_COUNT(STATUS, DATATYPE, COUNT, IERROR) INTEGER STATUS(MPI_STATUS_SIZE), DATATYPE, COUNT, IERROR Uzyskiwanie informacji o odbieranej wiadomości z tablicy status

Przykład: obliczanie drugich pochodnych funkcji jednej zmiennej metodą ilorazów różnicowych

integer i,n double precision pi parameter(pi=3.1415, n=100) double precision x(n),dx,xx(n) dx=2*pi/n do i=1,n x(i)=cos(i*dx) enddo do i=2,n-1 xx(i)=(x(i-1)-2*x(i)+x(i+1))/(dx*dx) enddo xx(1)=(x(n)-2*x(1)+x(2))/(dx*dx) xx(n)=(x(n-1)-2*x(n)+x(1))/(dx*dx) do i=1,n print *,i*dx,x(i),xx(i) enddo stop end f(x) = cos(x) Przedział [0,2  ] dzielimy na n części. Przy obliczaniu ilorazów różnicowych odpowiadających punktom z końców przedziału korzystamy z okresowości funkcji cosinus. Kod szeregowy (Fortran 77) Kolor czerwony: funkcja; kolor zielony: druga pochodna

Podział punktów siatki pomiędzy procesory oraz zaplanowanie komunikacji Każdy procesor dostaje do obliczenia wartości funkcji w m kolejnych punktach; całkowita liczba punktów wynosi m*nproc. Każdy procesor oblicza wartości ilorazów różnicowych w przypisanych sobie punktach; do obliczenia ilorazu w punkcie pierwszym pobiera wartość funkcji od lewego sąsiada a do obliczenia ilorazu w punkcie ostatnim od prawego sąsiada; analogicznie wysyła swoim sąsiadom wymagane przez nich dane. Lewym sąsiadem procesora pierwszego jest procesor ostatni a prawym sąsiadem ostatniego pierwszy (periodyczność). Każdy procesor wykonuje sumarycznie dwie operacje wysyłania i dwie odbioru.

include 'mpif.h' integer ierr,rank,size,req(4),stat(MPI_STATUS_SIZE) integer i,n double precision pi parameter(pi=3.1415, n=10) double precision x(n),dx,y(n),right,left call MPI_INIT( ierr ) call MPI_COMM_RANK( MPI_COMM_WORLD, rank, ierr ) call MPI_COMM_SIZE( MPI_COMM_WORLD, size, ierr ) dx=2*pi/(n*size) do i=1,n x(i)=cos(n*dx*rank+i*dx) enddo iright=mod(size+rank+1,size) ileft=mod(size+rank-1,size) call MPI_SEND(x(1),1,MPI_DOUBLE_PRECISION, ileft, & 1, MPI_COMM_WORLD, ierr) call MPI_SEND(x(n),1,MPI_DOUBLE_PRECISION, iright, & 1, MPI_COMM_WORLD, ierr) call MPI_RECV(left,1,MPI_DOUBLE_PRECISION, ileft, & 1, MPI_COMM_WORLD, stat, ierr) call MPI_RECV(right,1,MPI_DOUBLE_PRECISION, iright, & 1, MPI_COMM_WORLD, stat, ierr) do i=2,n-1 y(i)=(x(i-1)-2*x(i)+x(i+1))/(dx*dx) enddo y(1)=(left-2*x(1)+x(2))/(dx*dx) y(n)=(x(n-1)-2*x(n)+right)/(dx*dx) do i=1,n print *,rank,n*dx*rank+i*dx,x(i),y(i) enddo call MPI_FINALIZE(ierr) stop end Pierwsza wersja kodu równoległego – prosta ale nie spełnia wymogów bezpieczeństwa

call MPI_SEND(x(n),1,MPI_DOUBLE_PRECISION, iright, & 1, MPI_COMM_WORLD, ierr) call MPI_RECV(left,1,MPI_DOUBLE_PRECISION, ileft, & 1, MPI_COMM_WORLD, stat, ierr) call MPI_SEND(x(1),1,MPI_DOUBLE_PRECISION, ileft, & 1, MPI_COMM_WORLD, ierr) call MPI_RECV(right,1,MPI_DOUBLE_PRECISION, iright, & 1, MPI_COMM_WORLD, stat, ierr) Wersja bardziej bezpieczna – odpowiednia kolejność wysyłania i odbioru Wersja niedziałająca (deadlock) call MPI_RECV(left,1,MPI_DOUBLE_PRECISION, ileft, & 1, MPI_COMM_WORLD, stat, ierr) call MPI_RECV(right,1,MPI_DOUBLE_PRECISION, iright, & 1, MPI_COMM_WORLD, stat, ierr) call MPI_SEND(x(n),1,MPI_DOUBLE_PRECISION, iright, & 1, MPI_COMM_WORLD, ierr) call MPI_SEND(x(1),1,MPI_DOUBLE_PRECISION, ileft, & 1, MPI_COMM_WORLD, ierr)

W sytuacjach takich jak w powyższym przykładzie dla uproszczenia kodu warto zastosować funkcję MPI_Sendrecv łączącą operacje wysyłania i odbioru MPI_SENDRECV(sendbuf, sendcount, sendtype, dest, sendtag, recvbuf, recvcount, recvtype, source, recvtag, comm, status) sendbuf – początkowy adres bufora wysyłanych danych; sendcount – liczba elementów wysyłanych danych; sendtype – typ danych wysyłanych; dest – rząd procesu celowego; sendtag – pieczątka wysyłanej wiadomości; recvbuf – początkowy adres bufora odbieranej wiadomości; recvcount – liczba elementów odbieranych danych; recvtype – typ odbieranych danych; source – rząd procesu wysyłającego; recvtag - pieczątka odbieranej wiadomości; comm – komunikator; status – status odebranej wiadomości.

C: int MPI_Sendrecv(void *sendbuf, int sendcount, MPI_Datatype sendtype, int dest, int sendtag, void *recvbuf, int recvcount, MPI_Datatype recvtype, int source, MPI_Datatype recvtag, MPI_Comm comm, MPI_Status *status) Fortran: MPI_SENDRECV(SENDBUF, SENDCOUNT, SENDTYPE, DEST, SENDTAG, RECVBUF, RECVCOUNT, RECVTYPE, SOURCE, RECVTAG, COMM, STATUS, IERROR) SENDBUF(*), RECVBUF(*) INTEGER SENDCOUNT, SENDTYPE, DEST, SENDTAG, RECVCOUNT, RECVTYPE, SOURCE, RECVTAG, COMM, STATUS(MPI_STATUS_SIZE), IERROR

call MPI_SENDRECV(x(n),1,MPI_DOUBLE_PRECISION, iright, & 1, left,1,MPI_DOUBLE_PRECISION, ileft,1, & MPI_COMM_WORLD, stat, ierr) call MPI_SENDRECV(x(1),1,MPI_DOUBLE_PRECISION, ileft, & 2, right,1,MPI_DOUBLE_PRECISION, iright,2, & MPI_COMM_WORLD, stat, ierr) call MPI_SENDRECV(x(1),1,MPI_DOUBLE_PRECISION, ileft, & 1, left,1,MPI_DOUBLE_PRECISION, ileft,1, & MPI_COMM_WORLD, stat, ierr) call MPI_SENDRECV(x(n),1,MPI_DOUBLE_PRECISION, iright, & 1, right,1,MPI_DOUBLE_PRECISION, iright,1, & MPI_COMM_WORLD, stat, ierr) Prawidłowy kod wykorzystujący MPI_Sendrecv (wysyłamy do innego procesu niż ten, od którego odbieramy) Nieprawidłowy kod wykorzystujący MPI_Sendrecv prowadzący do zablokowania (wysyłamy do tego samego procesu co ten, od którego odbieramy)

Zaawansowane zagadnienia komunikacji punktowej

MPI_ISEND(buf, count, datatype, dest, tag, comm, request) buf, count, datatype, dest, tag, comm – jak dla wysyłania wstrzymującego request – zmienna mówiąca o stanie zakończenia operacji wysyłania Niewstrzymujące wysyłanie wiadomości C: int MPI_Isend(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request) Fortran 77: MPI_ISEND(BUF, COUNT, DATATYPE, DEST, TAG, COMM, REQUEST,IERROR) BUF(*) INTEGER COUNT, DATATYPE, DEST, TAG, COMM, REQUEST, IERROR

Niewstrzymujące odbieranie wiadomości MPI_IRECV (buf, count, datatype, source, tag, comm, request) C: int MPI_Irecv(void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Request *request) Fortran: MPI_IRECV(BUF, COUNT, DATATYPE, SOURCE, TAG, COMM, REQUEST, IERROR) BUF(*) INTEGER COUNT, DATATYPE, SOURCE, TAG, COMM, REQUEST, IERROR

Funkcje stowarzyszone z funkcjami niewstrzymującego wysyłania/odbioru MPI_WAIT(request, status) – oczekiwanie na zakończenie wysyłania/odbioru zadanego przez request; MPI_WAITANY (count, array_of_requests, index, status) – oczekiwanie na zakończenie któregokolwiek z procesów wysyłania/odbioru zadanych w tablicy array_of_requests, status pierwszego zakończonego procesu jest w zmiennej status; MPI_WAITSOME(incount, array_of_requests, outcount, array_of_indices, array_of_statuses) – oczekiwanie na zakończenie któregokolwiek z procesów wysyłania/odbioru zadanych w tablicy array_of_requests, statusy zakończonych procesów są zebrane w tablicy array_of_statuses; MPI_WAITALL( count, array_of_requests, array_of_statuses) – oczekiwanie na zakończenie wszystkich procesów wysyłania/odbioru zadanych w tablicy array_of_requests.

MPI_TEST(request, flag, status) – sprawdzanie stanu wykonania operacji wysyłania/odbioru danej przez request; MPI_TESTANY(count, array_of_requests, index, flag, status) MPI_TESTALL(count, array_of_requests, flag, array_of_statuses) MPI_PROBE(source, tag, comm, status) – sprawdzenie stanu operacji wysłania/odbioru pochodzącej od procesora source o pieczątce tag; MPI_IPROBE(source, tag, comm, flag, status) – jak wyżej tylko niewstrzymująca.

integer ierr,rank,size,req(4),stat(MPI_STATUS_SIZE) iright=mod(size+rank+1,size) ileft=mod(size+rank-1,size) call MPI_IRECV(left,1,MPI_DOUBLE_PRECISION, ileft, & 1, MPI_COMM_WORLD, req(1), ierr) call MPI_IRECV(right,1,MPI_DOUBLE_PRECISION, iright, & 1, MPI_COMM_WORLD, req(2), ierr) call MPI_ISEND(x(1),1,MPI_DOUBLE_PRECISION, ileft, & 1, MPI_COMM_WORLD, req(3), ierr) call MPI_ISEND(x(n),1,MPI_DOUBLE_PRECISION, iright, & 1, MPI_COMM_WORLD, req(4), ierr) do i=2,n-1 y(i)=(x(i-1)-2*x(i)+x(i+1))/(dx*dx) enddo call MPI_WAITALL(2,req,stat,ierr)

Kolejność odbieranych wiadomości Zgodność typów przysyłanych danych –MPI_BYTE też musi być zgodny u nadawcy i odbiorcy –wyjątek MPI_PACKED –automatyczna konwersja w środowisku heterogenicznym, ale nie zdefiniowana pomiędzy językami np. C-Fortran Zgodność identyfikatorów –tag= 0...MPI_TAG_UB>=32767 Wielkość odbieranej wiadomości nie musi być zgodna z rozmiarem bufora odbiorcy (błąd w przypadku przepełnienia); MPI_PROBE pozwala sprawdzić wielkośćwiadomości bez jej odbierania

Poprawny kod CALL MPI_COMM_RANK(comm, rank, ierr) IF(rank.EQ.0) THEN CALL MPI_SEND(a(1), 10, MPI_REAL, 1, tag, comm, ierr) ELSE CALL MPI_RECV(b(1), 15, MPI_REAL, 0, tag, comm, status, ierr) END IF Błędny kod CALL MPI_COMM_RANK(comm, rank, ierr) IF(rank.EQ.0) THEN CALL MPI_SEND(a(1), 10, MPI_REAL, 1, tag, comm, ierr) ELSE CALL MPI_RECV(b(1), 40, MPI_BYTE, 0, tag, comm, status, ierr) END IF

MPI_PACK i MPI_UNPACK Funkcja MPI_PACK umieszcza dane różnych typów w buforze zdefiniowanym przez użytkownika który pózniej można przesłać korzystając z typu MPI_PACKED Wiadomość o dowolnym typie można wysłać/odebrać korzystając z typu MPI_PACKED Funkcja MPI_UNPACK umieszcza odebrane dane typu MPI_PACKED w zmiennej określonego typu

MPI_PACK(inbuf, incount, datatype, outbuf, outsize, position, comm) [ IN inbuf] zmienna zawierając dane wejściowe [ IN incount] ilość danych wejsciowych [ IN datatype] typ danych wejściowych [ OUT outbuf] bufor danych [ IN outsize] rozmiar bufora danych w bajtach [ INOUT position] bierząca pozycja w buforze w bajtach [ IN comm] komunikator int MPI_Pack(void* inbuf, int incount, MPI_Datatype datatype, void *outbuf, int outsize, int *position, MPI_Comm comm) MPI_PACK(INBUF, INCOUNT, DATATYPE, OUTBUF, OUTSIZE, POSITION, COMM, IERROR) INBUF(*), OUTBUF(*) INTEGER INCOUNT, DATATYPE, OUTSIZE, POSITION, COMM, IERROR

MPI_UNPACK(inbuf, insize, position, outbuf, outcount, datatype, comm) [ IN inbuf] bufor zawierając dane wejściowe [ IN insize] rozmiar bufora w bajtach [ INOUT position] bierząca pozycja w buforze w bajtach [ OUT outbuf] zmienna dla danych wyjściowych [ IN outcount] ilość danych wyjściowych [ IN datatype] typ danych wyjściowych [ IN comm] komunikator int MPI_Unpack(void* inbuf, int insize, int *position, void *outbuf, int outcount, MPI_Datatype datatype, MPI_Comm comm) MPI_UNPACK(INBUF, INSIZE, POSITION, OUTBUF, OUTCOUNT, DATATYPE, COMM, IERROR) INBUF(*), OUTBUF(*) INTEGER INSIZE, POSITION, OUTCOUNT, DATATYPE, COMM, IERROR

MPI_PACK_SIZE(incount, datatype, comm, size) [ IN incount] ilość danych wejściowych dla MPI_PACK [ IN datatype] typ danych wejściowych [ IN comm] komunikator [ OUT size] potrzebny rozmiar bufora w MPI_PACK w bajtach int MPI_Pack_size(int incount, MPI_Datatype datatype, MPI_Comm comm, int *size) MPI_PACK_SIZE(INCOUNT, DATATYPE, COMM, SIZE, IERROR) INTEGER INCOUNT, DATATYPE, COMM, SIZE, IERROR

int position, i, j, a[2]; char buff[1000];.... MPI_Comm_rank(MPI_COMM_WORLD, &myrank); if (myrank == 0) { / * SENDER CODE */ 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) }

● Działanie funkcji biblioteki MPI jest zdefiniowane przez specyfikację MPI Standard 1.1 i 2.0 a nie przez konkretną implementację ● Implementacje mogą się różnić w szczegółach nie zdefiniowanych przez MPI Standard, co pozwala na dostosowanie implementacji do sprzętu (np. MPICHG2 jest dostosowany do pracy w rozproszonym środowisku GRID) ● Nie należy korzystać z cech obecnych jedynie w konkretnej implementacji przy tworzeniu przenośnych kodów równoległych

Tryb standardowy Specyfikacja nie definiuje czy komunikacja bedzie buforowana czy nie Charakter nielokalny - zakończenie może zależeć od odbiorcy Większość implementacji zapewnia buforowanie w zależności od rozmiaru wiadomości Przenośne programy nie powinny bazować na buforowaniu w trybie standardowym

Tryb buforowany Bufor u nadawacy jest definiowany przez programistę MPI_BUFFER_ATTACH Charakter lokalny - zakończenie zależy jedynie od odbiorcy Może doprowadzić do podwójnego buforowania i związanych z tym dodatkowych operacji dostępu do pamięci MPI_PACK_SIZE + MPI_BSEND_OVERHEAD

MPI_BUFFER_ATTACH( buffer, size) [ IN buffer] początkowy adres bufora [ IN size] rozmiar bufora w bajtach int MPI_Buffer_attach( void* buffer, int size) MPI_BUFFER_ATTACH( BUFFER, SIZE, IERROR) BUFFER(*) INTEGER SIZE, IERROR MPI_BUFFER_DETACH( buffer_addr, size) [ OUT buffer_addr] początkowy adres bufora [ OUT size] rozmiar bufora w bajtach int MPI_Buffer_detach( void* buffer_addr, int* size) MPI_BUFFER_DETACH( BUFFER_ADDR, SIZE, IERROR) BUFFER_ADDR(*) INTEGER SIZE, IERROR

Tryb synchroniczny Brak buforowania u nadawacy Charakter nielokalny - zakończenie zależy zawsze od odbiorcy Zakończenie działania u nadawcy gwarantuje że odbiorca rozpoczął przyjmowanie wiadomości W przypadku komunikacji blokującej u nadawcy i odbiorcy zapewnia pełną synchronizację komunikacji

Tryb gotowości Może się rozpocząć tylko jeśli odbiorca już czeka na wiadomość, w innym przypadku zachowanie jest nie zdefiniowane przez specyfikację Charakter lokalny - zakończenie operacji nie gwarantuje ze wiadomość jest odebrana Pozwala zaoszczędzić część operacji synchronizacji (handshaking) W poprawnym programie zastapienie MPI_Rsend przez MPI_Send nie powinno wpłynąć na efekt działania poza szybkością komunikacji

Przykładowa implementacja Tryb gotowości - wysyłanie natychmiastowe Tryb synchroniczny - nadawca wysyła najpierw prośbę o potwierdzenie gotowości odbiorcy Tryb standardowy - dla małych wiadomości wysyłanie natychmiastowe z buforowaniem na poziomie sprzętu a dla większych jak w trybie synchronicznym Tryb buforowany - skopiowanie wiadomości do bufora i wykonanie wysyłania nieblokującego

CALL MPI_COMM_RANK(comm, rank, ierr) IF (rank.EQ.0) THEN CALL MPI_BSEND(buf1, count, MPI_REAL, 1, tag, comm, ierr) CALL MPI_BSEND(buf2, count, MPI_REAL, 1, tag, comm, ierr) ELSE ! rank.EQ.1 CALL MPI_RECV(buf1, count, MPI_REAL, 0, MPI_ANY_TAG, comm, status, ierr) CALL MPI_RECV(buf2, count, MPI_REAL, 0, tag, comm, status, ierr) END IF CALL MPI_COMM_RANK(comm, rank, ierr) IF (rank.EQ.0) THEN CALL MPI_BSEND(buf1, count, MPI_REAL, 1, tag1, comm, ierr) CALL MPI_SSEND(buf2, count, MPI_REAL, 1, tag2, comm, ierr) ELSE ! rank.EQ.1 CALL MPI_RECV(buf1, count, MPI_REAL, 0, tag2, comm, status, ierr) CALL MPI_RECV(buf2, count, MPI_REAL, 0, tag1, comm, status, ierr) END

double precision x(n),dx,y(n),right,left,bufor(100)..... call MPI_BUFFER_ATTACH(bufor,100,ierr)..... call MPI_BSEND(x(n),1,MPI_DOUBLE_PRECISION, iright, & 1, MPI_COMM_WORLD, ierr) call MPI_BSEND(x(1),1,MPI_DOUBLE_PRECISION, ileft, & 1, MPI_COMM_WORLD, ierr) call MPI_RECV(left,1,MPI_DOUBLE_PRECISION, ileft, & 1, MPI_COMM_WORLD, stat, ierr) call MPI_RECV(right,1,MPI_DOUBLE_PRECISION, iright, & 1, MPI_COMM_WORLD, stat, ierr) Wersja bezpieczna – buforowanie danych w MPI_BSEND

call MPI_SSEND(x(n),1,MPI_DOUBLE_PRECISION, iright, & 1, MPI_COMM_WORLD, ierr) call MPI_RECV(left,1,MPI_DOUBLE_PRECISION, ileft, & 1, MPI_COMM_WORLD, stat, ierr) call MPI_SSEND(x(1),1,MPI_DOUBLE_PRECISION, ileft, & 1, MPI_COMM_WORLD, ierr) call MPI_RECV(right,1,MPI_DOUBLE_PRECISION, iright, & 1, MPI_COMM_WORLD, stat, ierr) Wersja bardziej bezpieczna – odpowiednia kolejność wysyłania i odbioru lecz nie zadziała w trybie synchronicznym (deadlock)

if (mod(rank,2).eq.0) then call MPI_SSEND(x(n),1,MPI_DOUBLE_PRECISION, iright, & 1, MPI_COMM_WORLD, ierr) call MPI_RECV(left,1,MPI_DOUBLE_PRECISION, ileft, & 1, MPI_COMM_WORLD, stat, ierr) call MPI_SSEND(x(1),1,MPI_DOUBLE_PRECISION, ileft, & 1, MPI_COMM_WORLD, ierr) call MPI_RECV(right,1,MPI_DOUBLE_PRECISION, iright, & 1, MPI_COMM_WORLD, stat, ierr) else call MPI_RECV(left,1,MPI_DOUBLE_PRECISION, ileft, & 1, MPI_COMM_WORLD, stat, ierr) call MPI_SSEND(x(n),1,MPI_DOUBLE_PRECISION, iright, & 1, MPI_COMM_WORLD, ierr) call MPI_RECV(right,1,MPI_DOUBLE_PRECISION, iright, & 1, MPI_COMM_WORLD, stat, ierr) call MPI_SSEND(x(1),1,MPI_DOUBLE_PRECISION, ileft, & 1, MPI_COMM_WORLD, ierr) endif

Tryb gotowości

Gorąca linia (persistent communication) Pozwala na dodatkową optymalizację przesyłania danych jeśli lista argumentów jest taka sama Rozdzielenie inicjalizacji gorącej linii (funkcja MPI_SEND_INIT i MPI_RECV_INIT) od samego przesyłania danych MPI_START Wyłączenie gorącej linii (MPI_REQUEST_FREE) jest możliwe po zakończeniu komunikacji Możliwe jest użycie różnych trybów komunikacji Gorąca może współpracować ze zwyczajnymi instrukcjami komunikacji punktowej

MPI_SEND_INIT(buf, count, datatype, dest, tag, comm, request) [ IN buf] zmienna do wysłania [ IN count] ilość elementów do wysłania [ IN datatype] typ elementów [ IN dest] odbiorca [ IN tag] identyfikator wiadomości [ IN comm] komunikator [ OUT request] wskaźnik pozwalający na dowołanie się do tej instrukcji int MPI_Send_init(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request) MPI_SEND_INIT(BUF, COUNT, DATATYPE, DEST, TAG, COMM, REQUEST, IERROR) BUF(*) INTEGER REQUEST, COUNT, DATATYPE, DEST, TAG, COMM, REQUEST, IERROR

MPI_RECV_INIT( buf, count, datatype, source, tag, comm, request) [ OUT buf] zmienna dla odbieranych danych [ IN count] ilość elementów [ IN datatype] typ danych [ IN source] nadawca [ IN tag] identyfikator wiadomości [ IN comm] komunikator [ OUT request] wskaźnik pozwalający na dowołanie się do tej instrukcji int MPI_Recv_init(void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Request *request) MPI_RECV_INIT(BUF, COUNT, DATATYPE, SOURCE, TAG, COMM, REQUEST, IERROR) BUF(*) INTEGER COUNT, DATATYPE, SOURCE, TAG, COMM, REQUEST, IERROR

MPI_START(request) [ INOUT request] wskaźnik do instrukji inicjalizującej gorąca linię int MPI_Start(MPI_Request *request) MPI_START(REQUEST, IERROR) INTEGER REQUEST, IERROR MPI_STARTALL( count, array_of_requests) [ IN count] wielkość tablicy ze wskaźnikami [ INOUT array_of_requests] tablica ze wskaźnik do instrukji inicjalizującej gorąca linię int MPI_Startall(int count, MPI_Request *array_of_requests) MPI_STARTALL(COUNT, ARRAY_OF_REQUESTS, IERROR) INTEGER COUNT, ARRAY_OF_REQUESTS(*), IERROR