MPI: Blockieren gegen Nichtblockieren


72

Ich habe Probleme, das Konzept der blockierenden und nicht blockierenden Kommunikation in MPI zu verstehen. Was sind die Unterschiede zwischen den beiden? Was sind die Vor- und Nachteile?

Antworten:


98

Das Blockieren der Kommunikation erfolgt mit MPI_Send()und MPI_Recv(). Diese Funktionen kehren erst zurück (dh sie blockieren), wenn die Kommunikation beendet ist. Etwas vereinfacht bedeutet dies, dass der übergebene Puffer MPI_Send()wiederverwendet werden kann, entweder weil MPI ihn irgendwo gespeichert hat oder weil er vom Ziel empfangen wurde. In ähnlicher Weise wird zurückgegeben, MPI_Recv()wenn der Empfangspuffer mit gültigen Daten gefüllt wurde.

Im Gegensatz dazu erfolgt die nicht blockierende Kommunikation mit MPI_Isend()und MPI_Irecv(). Diese Funktionen kehren sofort zurück (dh sie blockieren nicht), auch wenn die Kommunikation noch nicht beendet ist. Sie müssen anrufen MPI_Wait()oderMPI_Test() sehen, ob die Kommunikation beendet ist.

Blocking communication is used when it is sufficient, since it is somewhat easier to use. Non-blocking communication is used when necessary, for example, you may call MPI_Isend(), do some computations, then do MPI_Wait(). This allows computations and communication to overlap, which generally leads to improved performance.

Note that collective communication (e.g., all-reduce) is only available in its blocking version up to MPIv2. IIRC, MPIv3 introduces non-blocking collective communication.

A quick overview of MPI's send modes can be seen here.


2
so a MPI_Send() is the same as MPI_Isend() + MPI_Wait()?
lamba

2
Yes, except overhead, you can think of MPI_Send() as an MPI_Isend() followed by an MPI_Wait().
user1202136

16
@user1202136: You might want to mention that MPI_Send completes when you can reuse the buffer, independently of whether the receiver has received the data (or even of whether the data has been sent yet at all).
Jeremiah Willcock

according to this explanation, MPI_SEND() would be the same as MPI_SSEND(). That's not the case.
nz_21

58

This post, although is a bit old, but I contend the accepted answer. the statement " These functions don't return until the communication is finished" is a little misguiding because blocking communications doesn't guarantee any handshake b/w the send and receive operations.

Zunächst muss man wissen, dass das Senden vier Kommunikationsmodi hat: Standard, Gepuffert, Synchron und Bereit. Jeder dieser Modi kann blockierend und nicht blockierend sein

Im Gegensatz zum Senden hat das Empfangen nur einen Modus und kann blockierend oder nicht blockierend sein .

Bevor Sie fortfahren muss man auch klar sein , dass ich ausdrücklich erwähnen , welche ist eine MPI_Send \ Recv Puffer und das ist Systempuffer (die in jedem Prozessor ein lokaler Puffer von der MPI - Bibliothek im Besitz von Daten verwendet bewegen unter Reihen eines Kommunikations Gruppe)

BLOCKIERENDE KOMMUNIKATION : Das Blockieren bedeutet nicht, dass die Nachricht an den Empfänger / das Ziel gesendet wurde. Dies bedeutet einfach, dass der (Sende- oder Empfangspuffer) zur Wiederverwendung verfügbar ist. Um den Puffer wiederzuverwenden, reicht es aus, die Informationen in einen anderen Speicherbereich zu kopieren, dh die Bibliothek kann die Pufferdaten an den eigenen Speicherort in der Bibliothek kopieren und dann beispielsweise MPI_Send zurückgeben.

Der MPI-Standard macht es sehr deutlich, die Nachrichtenpufferung von Sende- und Empfangsvorgängen zu entkoppeln. Ein blockierendes Senden kann abgeschlossen werden, sobald die Nachricht gepuffert wurde, obwohl kein übereinstimmender Empfang gebucht wurde. In einigen Fällen kann die Nachrichtenpufferung jedoch teuer sein, und daher kann das direkte Kopieren vom Sendepuffer zum Empfangspuffer effizient sein. Daher bietet MPI Standard vier verschiedene Sendemodi, um dem Benutzer die Freiheit zu geben, den geeigneten Sendemodus für seine Anwendung auszuwählen. Schauen wir uns an, was in den einzelnen Kommunikationsarten passiert:

1. Standardmodus

Im Standard ist es Sache der MPI-Bibliothek, die ausgehende Nachricht zu puffern oder nicht. In dem Fall, in dem die Bibliothek beschließt, die ausgehende Nachricht zu puffern, kann das Senden abgeschlossen werden, noch bevor der übereinstimmende Empfang aufgerufen wurde. In dem Fall, in dem die Bibliothek beschließt, nicht zu puffern (aus Leistungsgründen oder aufgrund der Nichtverfügbarkeit des Pufferplatzes), wird der Sendevorgang erst zurückgegeben, wenn ein übereinstimmender Empfang gebucht und die Daten im Sendepuffer in den Empfangspuffer verschoben wurden.

Somit ist MPI_Send im Standardmodus nicht lokal in dem Sinne, dass das Senden im Standardmodus gestartet werden kann, unabhängig davon, ob ein übereinstimmender Empfang gebucht wurde oder nicht, und sein erfolgreicher Abschluss kann vom Auftreten eines übereinstimmenden Empfangs abhängen (aufgrund der Tatsache, dass es implementiert wird abhängig davon, ob die Nachricht gepuffert wird oder nicht).

Die Syntax für Standard-Send ist unten:

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

2. Gepufferter Modus

Wie im Standardmodus kann das Senden im gepufferten Modus unabhängig davon gestartet werden, dass ein übereinstimmender Empfang gebucht wurde und der Sendevorgang möglicherweise abgeschlossen ist, bevor ein übereinstimmender Empfang gebucht wurde. Der Hauptunterschied ergibt sich jedoch aus der Tatsache, dass die ausgehende Nachricht gepuffert werden muss , wenn das Senden angestarrt wird und kein übereinstimmender Empfang gesendet wird . Hinweis: Wenn der übereinstimmende Empfang gesendet wird, kann sich der gepufferte Sendevorgang gerne mit dem Prozessor treffen, der den Empfang gestartet hat. Falls jedoch kein Empfang erfolgt, muss der gesendete Sendevorgang die ausgehende Nachricht puffern, damit der Sendevorgang abgeschlossen werden kann. In seiner Gesamtheit ist ein gepufferter Send lokal . Die Pufferzuordnung ist in diesem Fall benutzerdefiniert und bei unzureichendem Pufferplatz tritt ein Fehler auf.

Syntax für Buffer Send:

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

3. Synchroner Modus

Im synchronen Sendemodus kann das Senden gestartet werden, unabhängig davon, ob ein übereinstimmender Empfang gebucht wurde oder nicht. Der Sendevorgang wird jedoch nur dann erfolgreich abgeschlossen, wenn ein übereinstimmender Empfang gebucht wurde und der Empfänger begonnen hat, die durch synchrones Senden gesendete Nachricht zu empfangen. Der Abschluss des synchronen Sendens zeigt nicht nur an, dass der Puffer im Senden wiederverwendet werden kann, sondern auch, dass der Empfangsprozess begonnen hat, die Daten zu empfangen. Wenn sowohl Senden als auch Empfangen blockiert sind, wird die Kommunikation an beiden Enden vor dem Rendezvous des kommunizierenden Prozessors nicht abgeschlossen.

Syntax für synchrones Senden:

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

4. Bereitschaftsmodus

Im Gegensatz zu den vorherigen drei Modi kann ein Sendebereitschaftsmodus nur gestartet werden, wenn der übereinstimmende Empfang bereits gebucht wurde. Der Abschluss des Sendens zeigt nichts über den übereinstimmenden Empfang an und sagt lediglich, dass der Sendepuffer wiederverwendet werden kann. Ein Senden, das den Bereitschaftsmodus verwendet, hat dieselbe Semantik wie der Standardmodus oder ein Synchronmodus mit den zusätzlichen Informationen zu einem übereinstimmenden Empfang. Ein korrektes Programm mit einem Bereitschaftsmodus kann durch synchrones Senden oder Standard-Senden ersetzt werden, ohne dass sich dies auf das Ergebnis auswirkt, abgesehen von Leistungsunterschieden.

Syntax für Ready Send:

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

Nachdem sie alle 4 Blockierungs-Sendungen durchlaufen haben, scheinen sie im Prinzip unterschiedlich zu sein, aber je nach Implementierung kann die Semantik eines Modus einem anderen ähnlich sein.

Zum Beispiel ist MPI_Send im Allgemeinen ein Blockierungsmodus, aber je nach Implementierung kopiert MPI_Send die ausgehende Nachricht vom Sendepuffer in den Systempuffer (was im modernen System meistens der Fall ist) und kehrt sofort zurück, wenn die Nachrichtengröße nicht zu groß ist. Schauen wir uns ein Beispiel an:

//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);
}

Schauen wir uns an, was im obigen Beispiel bei jedem Rang passiert

Rang 0 versucht, auf Rang 1 und Rang 2 zu senden und von Rang 1 und d 3 zu empfangen.

Rang 1 versucht, auf Rang 0 und Rang 3 zu senden und nichts von anderen Rängen zu erhalten

Rang 2 versucht, von Rang 0 zu empfangen und später eine Operation mit den im recv_buff empfangenen Daten durchzuführen.

Rang 3 versucht, auf Rang 0 zu senden und von Rang 1 zu empfangen

Wo Anfänger verwirren lassen, dass rank 0 sendet auf Rang 1 , aber Rang 1 begonnen hat keine Empfangsoperation daher die Kommunikation sollte blockieren oder Stall und die zweite Sende Anweisung in rank 0 sollte nicht ausgeführt werden (und das ist , was MPI In der Dokumentation wird betont, dass die Implementierung definiert ist, ob die ausgehende Nachricht gepuffert wird oder nicht. In den meisten modernen Systemen können solche Nachrichten kleiner Größe (hier Größe 1) leicht gepuffert werden, und MPI_Send gibt die nächste MPI_Send-Anweisung zurück und führt sie aus. Daher wird im obigen Beispiel, selbst wenn der Empfang in Rang 1 nicht gestartet wird, der erste MPI_Send in Rang 0 zurückkehren und seine nächste Anweisung ausführen.

In einer hypothetischen Situation, in der Rang 3 die Ausführung vor Rang 0 startet, kopiert er die ausgehende Nachricht in der ersten Sendeanweisung aus dem Sendepuffer in einen Systempuffer (in einem modernen System;)) und beginnt dann mit der Ausführung seiner Empfangsanweisung. Sobald Rang 0 seine beiden Sendeanweisungen beendet und mit der Ausführung seiner Empfangsanweisung beginnt, werden die von Rang 3 im System gepufferten Daten in den Empfangspuffer von Rang 0 kopiert.

Wenn in einem Prozessor eine Empfangsoperation gestartet wird und kein übereinstimmender Sendevorgang gesendet wird, wird der Prozess blockiert, bis der Empfangspuffer mit den erwarteten Daten gefüllt ist. In dieser Situation wird eine Berechnung oder eine andere MPI-Kommunikation blockiert / angehalten, sofern MPI_Recv nicht zurückgegeben wurde.

Nachdem man die Pufferungsphänomene verstanden hat , sollte man zurückkehren und mehr über MPI_Ssend nachdenken, das die wahre Semantik einer blockierenden Kommunikation hat. Selbst wenn MPI_Ssend die ausgehende Nachricht aus dem Sendepuffer in einen Systempuffer kopiert (der wiederum implementierungsdefiniert ist), muss beachtet werden, dass MPI_Ssend nicht zurückkehrt, es sei denn, der sendende Prozessor hat eine Bestätigung (im Low-Level-Format) vom Empfangsprozess empfangen.

Glücklicherweise hat MPI beschlossen, die Dinge für die Benutzer in Bezug auf den Empfang einfacher zu gestalten , und es gibt nur einen Empfang in Blocking Communication: MPI_Recv , der mit jedem der vier oben beschriebenen Sendemodi verwendet werden kann. Für MPI_Recv bedeutet Blockieren, dass der Empfang erst zurückgegeben wird, nachdem er die Daten in seinem Puffer enthält. Dies bedeutet, dass der Empfang erst abgeschlossen werden kann, nachdem ein übereinstimmender Sendevorgang gestartet wurde, aber nicht, ob er abgeschlossen werden kann, bevor der übereinstimmende Sendevorgang abgeschlossen ist.

Was bei solchen blockierenden Aufrufen passiert, ist, dass die Berechnungen angehalten werden, bis der blockierte Puffer freigegeben ist. Dies führt normalerweise zu einer Verschwendung von Rechenressourcen, da Send / Recv normalerweise Daten von einem Speicherort an einen anderen Speicherort kopiert, während die Register in der CPU inaktiv bleiben.

NICHT-BLOCKIERENDE KOMMUNIKATION : Bei nicht blockierender Kommunikation erstellt die Anwendung eine Kommunikationsanforderung zum Senden und / oder Empfangen, erhält ein Handle zurück und wird dann beendet. Das ist alles, was benötigt wird, um sicherzustellen, dass der Prozess ausgeführt wird. Dh die MPI-Bibliothek wird benachrichtigt, dass die Operation ausgeführt werden muss.

Für die Absenderseite ermöglicht dies eine überlappende Berechnung mit der Kommunikation.

Auf der Empfängerseite ermöglicht dies das Überlappen eines Teils des Kommunikationsaufwands, dh das direkte Kopieren der Nachricht in den Adressraum der Empfangsseite in der Anwendung.


1
Ich wünschte, ich hätte das vor zwei Monaten gelesen. Vielen Dank für die Erklärung - hat mir sehr geholfen.
mm8511

> MPI_Send kehrt zurück und führt die nächste MPI_Send-Anweisung aus. s / its / its
franklsf95

Sie sagen: "Während solcher Blockierungsaufrufe werden die Berechnungen angehalten, bis der blockierte Puffer freigegeben ist. Dies führt normalerweise zur Verschwendung von Rechenressourcen, da Send / Recv normalerweise Daten von einem Speicherort zu einem anderen Speicherort kopiert, während sich die Register registrieren." in CPU bleiben untätig. " Das verwirrt mich. Gibt es auch auf der Empfängerseite einen Systempuffer? Auf welchen blockierten Puffer beziehen Sie sich? Kann die Nachricht nicht einfach direkt vom Senderpuffer (oder vom Systempuffer auf dem Sender, falls gepuffert) zum Empfängerpuffer gehen, ohne einen Empfängersystempuffer zu verwenden?
Millemila

10

Bei der Verwendung der blockierenden Kommunikation müssen Sie auf das Senden und Empfangen von Anrufen achten. Sehen Sie sich beispielsweise diesen Code an

 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);
 }

Was passiert in diesem Fall?

  1. Prozess 0 sendet x an Prozess 1 und blockiert, bis Prozess 1 x empfängt.
  2. Prozess 1 sendet y an Prozess 0 und blockiert, bis Prozess 0 y empfängt, aber
  3. Prozess 0 wird so blockiert, dass Prozess 1 für unendlich blockiert, bis die beiden Prozesse beendet sind.

9
Früher habe ich das gedacht. Wenn ich jedoch MPI_Send auf meinem Computer verwende, ist das Problem möglicherweise komplizierter. Der obige Code funktioniert so, als könnte er eine Nachricht in den Puffer verschieben. Nur MPI_Ssend wird strikt blockiert , da es zurückkehrt, bis das Ziel die Nachricht empfängt. Die folgenden Links erklären, dass verschiedene Anbieter unterschiedliche Implementierungen auswählen. mcs.anl.gov/research/projects/mpi/sendmode.html
Kette

4

Es ist leicht.

Nicht blockierend bedeutet, dass die Berechnung und Übertragung von Daten für einen einzelnen Prozess gleichzeitig erfolgen kann.

Während Blockieren bedeutet, hey Kumpel, müssen Sie sicherstellen, dass Sie die Datenübertragung bereits beendet haben, und dann zurückkehren, um den nächsten Befehl zu beenden. Wenn also eine Übertragung gefolgt von einer Berechnung erfolgt, muss die Berechnung nach dem Erfolg der Übertragung erfolgen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.