MPI: blokowanie vs nieblokowanie

Mam problem ze zrozumieniem pojęcia blokowania komunikacji i nieblokowania komunikacji w MPI. Jakie są różnice między nimi? Jakie są zalety i wady?

Dzięki!

Author: peaceman, 2012-04-04

4 answers

Blokowanie komunikacji odbywa się za pomocą MPI_Send() oraz MPI_Recv(). Funkcje te nie zwracają (tzn. blokują) aż do zakończenia komunikacji. Upraszczając nieco, oznacza to, że bufor przekazany do MPI_Send() może być ponownie użyty, ponieważ MPI gdzieś go zapisał, lub ponieważ został odebrany przez miejsce docelowe. Podobnie, MPI_Recv() zwraca, gdy bufor odbierający został wypełniony poprawnymi danymi.

Natomiast komunikacja nieblokująca odbywa się za pomocą MPI_Isend() oraz MPI_Irecv(). Funkcje te zwracają się natychmiast (tzn. nie blokują), nawet jeśli komunikacja nie jest jeszcze zakończona. Musisz zadzwonić.MPI_Wait() lub MPI_Test() aby sprawdzić, czy komunikacja została zakończona.

Blokowanie komunikacji jest używane, gdy jest wystarczające, ponieważ jest nieco łatwiejsze w użyciu. Komunikacja nieblokująca jest używana, gdy jest to konieczne, na przykład możesz wywołać MPI_Isend(), wykonać kilka obliczeń, a następnie wykonać MPI_Wait(). Pozwala to na obliczenia i komunikacji do nakładania się, co generalnie prowadzi do poprawy wydajności.

Należy pamiętać, że komunikacja zbiorowa (np. all-reduce) jest dostępna tylko w wersji blokującej do MPIv2. IIRC, mpiv3 wprowadza nieblokującą komunikację zbiorową.

Krótki przegląd trybów wysyłania MPI można zobaczyć tutaj .

 65
Author: user1202136,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-13 09:15:15

Ten post, chociaż jest trochę stary, ale kwestionuję zaakceptowaną odpowiedź. stwierdzenie "te funkcje nie zwracają dopóki komunikacja nie zostanie zakończona" jest trochę mylące, ponieważ blokowanie komunikacji nie gwarantuje żadnego uścisku dłoni b / w operacji wysyłania i odbierania.

Najpierw trzeba wiedzieć, wysyłanie ma cztery tryby komunikacji: standardowy, buforowany, synchroniczny i gotowy i każdy z nich może być blokowanie oraz nieblokujące

W przeciwieństwie do wysyłania, odbiór ma tylko jeden tryb i może byćblokujący lubnie blokujący .

Zanim przejdziemy dalej, należy również wyjaśnić, że wyraźnie wspomnieć, który z nich jest mpi_send\Recv buffer i który z nich jest buforem systemowym (który jest lokalnym buforem w każdym procesorze posiadanym przez Bibliotekę MPI używaną do przenoszenia danych pomiędzy szeregami grupy komunikacyjnej)

Blokowanie Komunikacja : Blokowanie nie oznacza, że wiadomość została dostarczona do odbiorcy/miejsca docelowego. Oznacza to po prostu, że bufor (wysyłania lub odbierania) jest dostępny do ponownego użycia. Aby ponownie użyć bufora, wystarczy skopiować informacje do innego obszaru pamięci, tzn. biblioteka może skopiować dane bufora do własnej lokalizacji pamięci w bibliotece, a następnie, na przykład, mpi_send może powrócić.

Standard MPI wyraźnie oddziela buforowanie wiadomości od operacji wysyłania i odbierania. Blokowanie wysyłania może zostać zakończone natychmiast po buforowaniu wiadomości, nawet jeśli nie wysłano dopasowanego odbioru. Ale w niektórych przypadkach buforowanie wiadomości może być kosztowne i dlatego bezpośrednie kopiowanie z bufora wysyłania do bufora odbioru może być efektywne. Dlatego Standard MPI zapewnia cztery różne tryby wysyłania, aby dać użytkownikowi swobodę w wyborze odpowiedniego trybu wysyłania dla jej aplikacji. Spójrzmy na to, co dzieje się w każdym trybie komunikacji:

1. Tryb Standardowy

W trybie standardowym to biblioteka MPI decyduje o tym, czy buforować wychodzącą wiadomość. W przypadku, gdy biblioteka zdecyduje się buforować wiadomość wychodzącą, wysyłanie może zostać zakończone nawet przed wywołaniem pasującego odbioru. W przypadku, gdy biblioteka zdecyduje się nie buforować (ze względu na wydajność lub z powodu niedostępności miejsca w buforze), wysyłanie nie powróci, dopóki pasujący odbiór nie zostanie opublikowany, a dane w buforze wysyłania zostaną przeniesione do bufor odbiorczy.

Tak więc MPI_Send w trybie standardowym jest nielokalnym w tym sensie, że wysyłanie w trybie standardowym może być uruchomione niezależnie od tego, czy pasujący odbiór został wysłany, czy nie, a jego pomyślne zakończenie może zależeć od wystąpienia pasującego odbioru (ze względu na fakt, że jest to zależne od implementacji, czy wiadomość będzie buforowana, czy nie) .

Składnia standardowego wysyłania znajduje się poniżej:

int MPI_Send(const void *buf, int count, MPI_Datatype datatype, 
             int dest, int tag, MPI_Comm comm)

2. Tryb Buforowany

Jak w trybie standardowym, wysyłanie w trybie buforowanym może być rozpoczęte niezależnie od tego, czy pasujący odbiór został wysłany, a wysyłanie może zakończyć się przed wysłaniem pasującego odbioru. Jednak główna różnica wynika z faktu, że jeśli wysyłanie jest wyświetlane i nie ma dopasowanego odbioru, wiadomość wychodząca musi być buforowana. Uwaga jeśli dopasowanie odbioru jest wysłane, buforowane wysłanie może szczęśliwie spotkać się z procesorem, który rozpoczął odbiór, ale w przypadku braku odbioru, wysyłanie w trybie buforowanym musi buforować wiadomość wychodzącą, aby umożliwić zakończenie wysyłania. W całości buffered send to local . Alokacja bufora w tym przypadku jest zdefiniowana przez użytkownika i w przypadku niewystarczającej ilości miejsca w buforze występuje błąd.

Składnia bufora send:

int MPI_Bsend(const void *buf, int count, MPI_Datatype datatype,
             int dest, int tag, MPI_Comm comm)

3. Tryb Synchroniczny

W trybie synchronicznego wysyłania, wysyłanie może być uruchomione niezależnie od tego, czy został wysłany pasujący odbiór. Jednak wysyłanie zakończy się pomyślnie tylko wtedy, gdy dopasowanie odbioru zostało wysłane, a odbiorca zaczął odbierać wiadomość wysłaną przez synchroniczne wysyłanie. Zakończenie synchronicznego wysyłania oznacza nie tylko, że bufor w wysyłaniu może być ponownie wykorzystany, ale także fakt, że proces odbiorczy rozpoczął odbiór danych. Jeśli zarówno wysyłanie, jak i odbieranie są blokowane, komunikacja nie kończy się na obu końcach przed spotkaniem procesora komunikującego się.

Składnia synchronicznego wysyłania:

int MPI_Ssend(const void *buf, int count, MPI_Datatype datatype, int dest,
              int tag, MPI_Comm comm)

4. Gotowe Mode

W przeciwieństwie do poprzednich trzech trybów, wysyłanie w trybie gotowości może być uruchomione tylko wtedy, gdy pasujący odbiór został już wysłany. Zakończenie wysyłania nie wskazuje nic na temat dopasowania odbioru, a jedynie informuje, że bufor wysyłania może być ponownie użyty. Wysyłanie w trybie gotowości ma taką samą semantykę jak w trybie standardowym lub synchronicznym z dodatkowymi informacjami o dopasowanym odbiorze. Poprawny program z gotowym trybem komunikacji można zastąpić synchroniczne wysyłanie lub standardowe wysyłanie bez wpływu na wynik poza różnicą wydajności.

Składnia dla gotowego wysłania:

int MPI_Rsend(const void *buf, int count, MPI_Datatype datatype, int dest, 
              int tag, MPI_Comm comm)

Po przejściu przez wszystkie 4 blocking-send, mogą wydawać się zasadniczo różne, ale w zależności od implementacji semantyka jednego trybu może być podobna do drugiego.

Na przykład MPI_Send ogólnie jest trybem blokowania, ale w zależności od implementacji, jeśli rozmiar wiadomości nie jest zbyt duży, mpi_send skopiuje wiadomość wychodzącą od bufora wysyłania do bufora systemowego ('co ma najczęściej miejsce w nowoczesnym systemie) i natychmiast powraca. Spójrzmy na przykład poniżej:

//assume there are 4 processors numbered from 0 to 3
if(rank==0){
    tag=2;
    MPI_Send(&send_buff1, 1, MPI_DOUBLE, 1, tag, MPI_COMM_WORLD);
    MPI_Send(&send_buff2, 1, MPI_DOUBLE, 2, tag, MPI_COMM_WORLD);
    MPI_Recv(&recv_buff1, MPI_FLOAT, 3, 5, MPI_COMM_WORLD);
    MPI_Recv(&recv_buff2, MPI_INT, 1, 10, MPI_COMM_WORLD);
}

else if(rank==1){
     tag = 10;
    //receive statement missing, nothing received from proc 0
    MPI_Send(&send_buff3, 1, MPI_INT, 0, tag, MPI_COMM_WORLD);
    MPI_Send(&send_buff3, 1, MPI_INT, 3, tag, MPI_COMM_WORLD);
}

else if(rank==2){
    MPI_Recv(&recv_buff, 1, MPI_DOUBLE, 0, 2, MPI_COMM_WORLD);
    //do something with receive buffer
}

else{ //if rank == 3
    MPI_Send(send_buff, 1, MPI_FLOAT, 0, 5, MPI_COMM_WORLD);
    MPI_Recv(recv_buff, 1, MPI_INT, 1, 10, MPI_COMM_WORLD);
}

Spójrzmy na to, co dzieje się w każdej randze w powyższym przykładzie

Ranga 0 próbuje wysłać do rangi 1 i 2 oraz otrzymać od rangi 1 i 3.

Ranga 1 próbuje wysłać do rangi 0 i rangi 3 i nie odbierać niczego z innych Rang

Rank 2 próbuje otrzymać z rank 0 i później wykonaj jakąś operację z danymi otrzymanymi w recv_buff.

Ranga 3 próbuje wysłać do rangi 0 i otrzymać od rangi 1

Gdzie początkujący zaczynają się mylić jest to, że ranga 0 wysyła do rank 1, ale ranga 1 nie rozpoczęła żadnej operacji odbioru, stąd komunikacja powinna blokować lub wstrzymywać, a druga Instrukcja send w rank 0 nie powinna być w ogóle wykonywana (i to jest to, co dokumentacja MPI podkreśla, że jest to implementacja zdefiniowana, czy wiadomość wychodząca będzie buforowana lub nie). W większości nowoczesnych systemów takie komunikaty o małych rozmiarach (tutaj rozmiar to 1) będą łatwo buforowane, a MPI_Send zwróci i wykona następne polecenie MPI_Send. Stąd w powyższym przykładzie, nawet jeśli receive in rank 1 nie jest uruchomiony, 1st MPI_Send in rank 0 zwróci i wykona swoją następną instrukcję.

W hipotetycznej sytuacji, gdy ranga 3 rozpoczyna wykonywanie przed rangą 0, skopiuje wiadomość wychodzącą w pierwszym wysłaniu polecenie z bufora send do bufora systemowego (w nowoczesnym systemie;)), a następnie uruchom jego polecenie receive. Gdy tylko ranga 0 zakończy swoje dwie polecenia send i rozpocznie wykonywanie instrukcji receive, dane buforowane w systemie przez rangę 3 są kopiowane do bufora odbioru w randze 0.

W przypadku, gdy w procesorze jest uruchomiona operacja odbioru i nie zostanie wysłane żadne dopasowane wysłanie, proces zablokuje się, dopóki bufor odbioru nie zostanie wypełniony oczekiwanymi danymi. W tym sytuacja obliczenia lub inna komunikacja MPI zostanie zablokowana / zatrzymana, chyba że mpi_recv powróci.

Zrozumiawszy zjawiska buforowania , należy powrócić i pomyśleć więcej o MPI_Ssend, który ma prawdziwą semantykę komunikacji blokującej. Nawet jeśli mpi_ssend skopiuje wiadomość wychodzącą z bufora send do bufora systemowego( który ponownie jest zdefiniowany w implementacji), należy zauważyć, że MPI_Ssend nie powróci, chyba że ktoś potwierdzi (w formacie niskiego poziomu) od procesu odbioru został odebrany przez procesor wysyłający.

Na szczęście MPI zdecydowało, że będzie łatwiej dla użytkowników pod względem odbierania i w blokowaniu komunikacji jest tylko jeden odbiór : MPI_Recv i może być używany z dowolnym z czterech trybów wysyłania opisanych powyżej. Dla MPI_Recv, blokowanie oznacza , które odbiera zwraca dopiero po tym, jak zawiera dane w swoim buforze. Oznacza to, że odbieranie może zakończyć się dopiero po rozpoczęciu pasującego wysyłania, ale nie oznacza to, czy może zakończyć się przed zakończeniem pasującego wysyłania.

To, co dzieje się podczas takich wywołań blokujących, to to, że obliczenia są wstrzymywane do czasu uwolnienia zablokowanego bufora. Zwykle prowadzi to do marnotrawstwa zasobów obliczeniowych, ponieważ Send / Recv Zwykle kopiuje dane z jednej lokalizacji pamięci do innej, podczas gdy rejestry w procesorze pozostają bezczynne.

KOMUNIKACJA NIEBLOKUJĄCA : Do komunikacji Nieblokującej aplikacja tworzy Prośba o komunikację do wysyłania i / lub odbierania i odzyskuje uchwyt, a następnie kończy się. To wszystko, co jest potrzebne, aby zagwarantować, że proces zostanie wykonany. Tzn. biblioteka MPI jest powiadamiana o konieczności wykonania operacji.

Po stronie nadawcy pozwala to na nakładanie się obliczeń z komunikacją.

Po stronie odbiorczej pozwala to na nakładanie się części nagłówków komunikacji, tj. kopiowanie wiadomości bezpośrednio do przestrzeni adresowej odbiornika strona w aplikacji.

 14
Author: gajendra,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-06 21:33:08

Używając komunikacji blokującej musisz dbać o wysyłanie i odbieranie połączeń na przykład spójrz na ten kod

 if(rank==0)
 {
     MPI_Send(x to process 1)
     MPI_Recv(y from process 1)
 }
 if(rank==1)
 {
     MPI_Send(y to process 0);
     MPI_Recv(x from process 0);
 }

Co się dzieje w tym przypadku?

  1. proces 0 wysyła x do procesu 1 i blokuje, dopóki proces 1 nie otrzyma x.
  2. Proces 1 wysyła y do procesu 0 i blokuje dopóki proces 0 nie otrzyma y, ale
  3. proces 0 jest blokowany tak, że proces 1 blokuje nieskończoność, dopóki dwa procesy nie zostaną zabite.
 10
Author: peaceman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-08 11:53:38

To proste.

Nieblokowanie oznacza, że obliczenia i przesyłanie danych może odbywać się w tym samym czasie dla jednego procesu.

Podczas gdy blokowanie oznacza, Hej kolego, musisz upewnić się, że już skończyłeś przesyłanie danych, a następnie wrócić do końca następnego polecenia, co oznacza, że jeśli jest transfer, a następnie obliczenia, obliczenia muszą być po sukcesie przesyłania.

 3
Author: Shaowu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-23 06:12:37