MPIでのブロック通信と非ブロック通信の概念を理解するのに苦労しています。2つの違いは何ですか?長所と短所は何ですか?
5 に答える
通信のブロックは、とを使用して行われMPI_Send()
ますMPI_Recv()
。これらの関数は、通信が終了するまで戻りません(つまり、ブロックします)。MPI_Send()
少し単純化すると、これは、MPIがバッファをどこかに保存したか、宛先によって受信されたために、渡されたバッファを再利用できることを意味します。同様にMPI_Recv()
、受信バッファが有効なデータでいっぱいになったときに戻ります。
対照的に、ノンブロッキング通信はとを使用して行われMPI_Isend()
ますMPI_Irecv()
。これらの関数は、通信がまだ終了していなくても、すぐに戻ります(つまり、ブロックされません)。電話をかけるMPI_Wait()
かMPI_Test()
、通信が終了したかどうかを確認する必要があります。
ブロッキング通信は、使いやすいため、十分な場合に使用されます。必要に応じてノンブロッキング通信が使用されます。たとえば、を呼び出しMPI_Isend()
、いくつかの計算を実行してから、を実行できますMPI_Wait()
。これにより、計算と通信をオーバーラップさせることができ、一般的にパフォーマンスが向上します。
集合通信(たとえば、all-reduce)は、MPIv2までのブロッキングバージョンでのみ使用可能であることに注意してください。IIRC、MPIv3は、ノンブロッキングの集合通信を導入します。
MPIの送信モードの概要はこちらでご覧いただけます。
この投稿は少し古いですが、私は受け入れられた答えを主張します。「これらの関数は通信が終了するまで戻らない」という記述は、通信をブロックしても送受信操作のハンドシェイクが保証されないため、少し誤解を招きます。
最初に知っておく必要があるのは、送信には4つの通信モードがあります。標準、バッファ、同期、準備完了であり、これらはそれぞれブロッキングと非ブロッキングのどちらでもかまいません。
送信とは異なり、受信には1つのモードしかなく、ブロックまたは非ブロックにすることができます。
先に進む前に、どちらがMPI_Send \ Recvバッファーで、どちらがシステムバッファー(通信のランク間でデータを移動するために使用されるMPIライブラリが所有する各プロセッサーのローカルバッファー)であるかを明示的に言及することも明確にする必要があります。グループ)
通信のブロック:ブロックは、メッセージが受信者/宛先に配信されたことを意味するものではありません。これは単に、(送信または受信)バッファーが再利用できることを意味します。バッファを再利用するには、情報を別のメモリ領域にコピーするだけで十分です。つまり、ライブラリはバッファデータをライブラリ内の独自のメモリ位置にコピーしてから、たとえばMPI_Sendが戻ることができます。
MPI標準では、メッセージのバッファリングを送受信操作から切り離すことが非常に明確になっています。一致する受信が投稿されていなくても、メッセージがバッファリングされるとすぐに、ブロッキング送信を完了することができます。ただし、場合によっては、メッセージバッファリングにコストがかかる可能性があるため、送信バッファから受信バッファへの直接コピーが効率的である可能性があります。したがって、MPI Standardは、ユーザーがアプリケーションに適切な送信モードを自由に選択できるように、4つの異なる送信モードを提供します。通信の各モードで何が起こるかを見てみましょう:
1.標準モード
標準モードでは、送信メッセージをバッファリングするかどうかはMPIライブラリに依存します。ライブラリが送信メッセージをバッファリングすることを決定した場合、一致する受信が呼び出される前でも送信を完了することができます。ライブラリがバッファリングしないことを決定した場合(パフォーマンス上の理由、またはバッファスペースが利用できないため)、一致する受信がポストされ、送信バッファ内のデータが受信バッファに移動されるまで、送信は返されません。
したがって、標準モードのMPI_Sendは、一致する受信が投稿されているかどうかに関係なく標準モードでの送信を開始できるという意味で非ローカルであり、その正常な完了は、一致する受信の発生に依存する可能性があります(実装であるため)メッセージがバッファリングされるかどうかによって異なります)。
標準送信の構文は次のとおりです。
int MPI_Send(const void *buf, int count, MPI_Datatype datatype,
int dest, int tag, MPI_Comm comm)
2.バッファモード
標準モードと同様に、バッファモードでの送信は、一致する受信が投稿されているかどうかに関係なく開始でき、一致する受信が投稿される前に送信が完了する場合があります。ただし、主な違いは、送信が開始され、一致する受信が投稿されない場合、送信メッセージをバッファリングする必要があるという事実から生じます。一致する受信が投稿された場合、バッファ送信は受信を開始したプロセッサとうまくランデブーできますが、受信がない場合、バッファモードでの送信は送信を完了するために送信メッセージをバッファリングする必要があります。全体として、バッファリングされた送信はローカルです。この場合のバッファ割り当てはユーザー定義であり、バッファスペースが不足している場合はエラーが発生します。
バッファ送信の構文:
int MPI_Bsend(const void *buf, int count, MPI_Datatype datatype,
int dest, int tag, MPI_Comm comm)
3.同期モード
同期送信モードでは、一致する受信が投稿されたかどうかに関係なく、送信を開始できます。ただし、一致する受信が投稿され、受信者が同期送信によって送信されたメッセージの受信を開始した場合にのみ、送信は正常に完了します。同期送信の完了は、送信内のバッファーを再利用できることを示すだけでなく、受信プロセスがデータの受信を開始したことも示します。送信と受信の両方がブロックされている場合、通信プロセッサがランデブーする前に、通信はどちらの端でも完了しません。
同期送信の構文:
int MPI_Ssend(const void *buf, int count, MPI_Datatype datatype, int dest,
int tag, MPI_Comm comm)
4.レディモード
前の3つのモードとは異なり、準備完了モードでの送信は、一致する受信がすでに投稿されている場合にのみ開始できます。送信の完了は、一致する受信について何も示さず、送信バッファーを再利用できることを通知するだけです。レディモードを使用する送信は、標準モードまたは同期モードと同じセマンティクスを持ち、一致する受信に関する追加情報があります。通信の準備ができたモードの正しいプログラムは、パフォーマンスの違いを除けば結果に影響を与えずに、同期送信または標準送信に置き換えることができます。
レディセンドの構文:
int MPI_Rsend(const void *buf, int count, MPI_Datatype datatype, int dest,
int tag, MPI_Comm comm)
4つのブロッキング送信をすべて実行すると、原則的に異なるように見える場合がありますが、実装によっては、あるモードのセマンティクスが別のモードと類似している場合があります。
たとえば、MPI_Sendは一般にブロッキングモードですが、実装によっては、メッセージサイズが大きすぎない場合、MPI_Sendは送信メッセージを送信バッファーからシステムバッファーにコピーし(最近のシステムではほとんどの場合)、すぐに戻ります。以下の例を見てみましょう:
//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);
}
上記の例の各ランクで何が起こっているかを見てみましょう
ランク0 は、ランク1とランク2に送信し、ランク1とランク3から受信しようとしています。
ランク1はランク0とランク3に送信しようとしており、他のランクからは何も受信していません。
ランク2はランク0から受信しようとしており、後でrecv_buffで受信したデータを使用して何らかの操作を実行します。
ランク3はランク0に送信し、ランク1から受信しようとしています
初心者が混乱するのは、ランク0がランク1に送信しているが、ランク1は受信操作を開始していないため、通信がブロックまたはストールし、ランク0の2番目の送信ステートメントをまったく実行しないことです(これがMPIです)ドキュメントは、送信メッセージがバッファリングされるかどうかは実装によって定義されることを強調しています)。最新のシステムのほとんどでは、このような小さなサイズ(ここではサイズは1)のメッセージは簡単にバッファリングされ、MPI_Sendは次のMPI_Sendステートメントを返し実行します。したがって、上記の例では、ランク1の受信が開始されていなくても、ランク0の最初のMPI_Sendが返され、次のステートメントが実行されます。
ランク3がランク0の前に実行を開始するという架空の状況では、最初のsendステートメントの送信メッセージをsendバッファーからシステムバッファー(最新のシステムでは;))にコピーしてから、receiveステートメントの実行を開始します。ランク0が2つの送信ステートメントを終了し、受信ステートメントの実行を開始するとすぐに、ランク3によってシステムにバッファリングされたデータがランク0の受信バッファにコピーされます。
プロセッサで受信操作が開始され、一致する送信が送信されない場合、プロセスは、受信バッファが予期するデータでいっぱいになるまでブロックされます。この状況では、MPI_Recvが返されない限り、計算またはその他のMPI通信がブロック/停止されます。
バッファリング現象を理解したら、戻って、ブロッキング通信の真のセマンティクスを持つMPI_Ssendについてもっと考える必要があります。MPI_Ssendが送信メッセージを送信バッファーからシステムバッファー(これも実装定義)にコピーする場合でも、受信プロセスからの何らかの確認応答(低レベル形式)が送信プロセッサーによって受信されない限り、MPI_Ssendは返されないことに注意する必要があります。
幸い、MPIは、受信に関してユーザーにとって物事を簡単にすることを決定しました。ブロッキング通信には受信が1つだけあり、MPI_Recvであり、上記の4つの送信モードのいずれでも使用できます。MPI_Recvの場合、ブロッキングとは、バッファにデータが含まれた後にのみ受信が返されることを意味します。これは、受信が一致する送信が開始された後にのみ完了することができることを意味しますが、一致する送信が完了する前に受信が完了することができるかどうかを意味するものではありません。
このようなブロッキング呼び出し中に発生するのは、ブロックされたバッファーが解放されるまで計算が停止されることです。Send / Recvは通常、あるメモリ位置から別のメモリ位置にデータをコピーしますが、CPUのレジスタはアイドル状態のままであるため、これは通常、計算リソースの浪費につながります。
ノンブロッキング通信:ノンブロッキング通信の場合、アプリケーションは送信および/または受信の通信要求を作成し、ハンドルを取り戻して終了します。プロセスが実行されることを保証するために必要なのはこれだけです。つまり、操作を実行する必要があることがMPIライブラリに通知されます。
送信側の場合、これにより、通信と重複する計算が可能になります。
受信側の場合、これにより、通信オーバーヘッドの一部をオーバーラップさせることができます。つまり、アプリケーションの受信側のアドレス空間にメッセージを直接コピーできます。
ブロッキング通信を使用する場合は、通話の送受信に注意する必要があります。たとえば、このコードを見てください。
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);
}
この場合はどうなりますか?
- プロセス0はxをプロセス1に送信し、プロセス1がxを受信するまでブロックします。
- プロセス1はyをプロセス0に送信し、プロセス0がyを受信するまでブロックしますが、
- プロセス0はブロックされ、プロセス1は2つのプロセスが強制終了されるまで無限にブロックされます。
容易いものだ。
ノンブロッキングとは、単一のプロセスでデータの計算と転送を同時に行うことができることを意味します。
ブロッキングとは、データの転送がすでに完了していることを確認してから、次のコマンドを完了する必要があることを意味します。つまり、転送とそれに続く計算がある場合、計算は転送が成功した後に行う必要があります。
受け入れられた答えと他の非常に長い答えの両方が、利点として計算と通信の重複について言及しています。それは1.主な動機ではなく、2。達成するのが非常に難しいことです。ノンブロッキング通信の主な利点(および本来の動機)は、デッドロックが発生したり、プロセスが不必要にシリアル化されたりすることなく、複雑な通信パターンを表現できることです。
例:デッドロック:全員が受信を行い、次に、たとえばリングに沿って全員が送信を行います。これはハングします。
シリアル化:線形順序に沿って、最後を除くすべての人が右に送信し、次に最初を除くすべての人が左から受信を行います。これにより、すべてのプロセスが並列ではなく順次実行されます。