3

他の人の質問に対する長い回答をしばらく書いていましたが、回答を投稿する前に削除するだけでした。努力を無駄にしたくないので、質問と回答をここに投稿します。

特定のコンパイラでのみ機能する興味深い半分の解決策も見つけたので、送受信のデッドロックに関する単なる標準的な回答ではありません。

並行コースでは、マスター プロセス 0 がメッセージをすべてのスレーブに送信し、メッセージを左右の隣人 (プロセッサ ID +/- 1、左の隣人を持たないプロセッサ 0 と、右の隣人を持たない最後のプロセッサ ID を除きます)。ネイバーにメッセージを再送信した後、スレーブ プロセッサは、ジョブが終了したというコンフォメーションをマスター プロセッサに送信します。

演習は簡単ですが、コードに問題があります。プログラムの開始時に確認終了メッセージが表示されるためです...ここで何が問題なのかわかりません。私は fflush で試しましたが、実際には、プログラムの最後の行は受信後にのみコンソールに書き込まれるはずです。

誰かが何か考えがありますか?私は MPI/C の概念に慣れていないので、何か問題があるのでしょうか?

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mpi.h>

int main(int argc, char *argv[]){
    int np, myId;
    char send[100], recv[100];

    MPI_Init(&argc, &argv);

    MPI_Comm_size(MPI_COMM_WORLD, &np);
    MPI_Comm_rank(MPI_COMM_WORLD, &myId);

    MPI_Status stat;
    if(myId == 0){
        int t = sprintf(send, "hey!"); //MPI_get_processor_name
        for(int i = 1; i < np; i++){
            printf("send %d => %d\n", myId, i);
            fflush(stdout);
            MPI_Send(send, 50, MPI_CHAR, i, 0, MPI_COMM_WORLD);
        }

        for(int i = 1; i < np; i++){
            MPI_Recv(recv, 50, MPI_CHAR, i, 0, MPI_COMM_WORLD, &stat);
            printf("%s\n", recv);
            fflush(stdout);
        }


    }else{
        if(myId < (np - 1)){
            printf("send %d => %d\n", myId, myId + 1);
            fflush(stdout);
            MPI_Send(send, 50, MPI_CHAR, myId + 1, 0, MPI_COMM_WORLD);
        }

        if(myId > 1){
            printf("Envoie %d => %d\n", myId, myId - 1);
            fflush(stdout);
                    MPI_Send(send, 50, MPI_CHAR, myId - 1, 0, MPI_COMM_WORLD);
        }

        MPI_Recv(send, 50, MPI_CHAR, MPI_ANY_SOURCE, 0, MPI_COMM_WORLD, &stat); 

        printf("Réception %d <= %d\n", myId, 0);
        fflush(stdout);

        if(myId != (np - 1)){
            MPI_Recv(send, 50, MPI_CHAR, myId + 1, 0, MPI_COMM_WORLD, &stat);
            printf("Receive %d <= %d\n", myId, myId + 1);
            fflush(stdout);
        }

        if(myId != 1){
            MPI_Recv(send, 50, MPI_CHAR, myId - 1, 0, MPI_COMM_WORLD, &stat);
            printf("Receive %d <= %d\n", myId, myId - 1);
            fflush(stdout);
        }

        int t = sprintf(recv, "End for %d.", myId);
        MPI_Send(recv, 50 , MPI_CHAR, 0, 0, MPI_COMM_WORLD); 
    }

    MPI_Finalize();
    return 0;
}
4

1 に答える 1

6

解決策 1

0 以外のすべての「スレーブ」コアが実際に行っていることと、あなたが言うべきことを比較してみましょう。

してもらいたいこと:

マスター プロセス 0 はすべてのスレーブにメッセージを送信し、メッセージを左右の隣人 (プロセッサ ID +/- 1、左隣人を持たないプロセッサ 0 と左隣人を持たない最後のプロセッサ ID を除く) に再送信します。右の隣人がいない)。ネイバーにメッセージを再送信した後、スレーブ プロセッサは、ジョブが終了したというコンフォメーションをマスター プロセッサに送信します。

コードの概要:

Send_To_Right_Neighbour();

Send_To_Left_Neighbour();

Receive_From_Master();

Receive_From_Right_Neighbour();

Receive_From_Left_Neighbour();

Send_To_Master();

違いを見ます?スレーブは、隣人に再送信する前にマスターからメッセージを受信して​​いません。コードを次のように変更します。

Receive_From_Master();

Send_To_Right_Neighbour();

Send_To_Left_Neighbour();

Receive_From_Right_Neighbour();

Receive_From_Left_Neighbour();

Send_To_Master();

それを修正すると、コードが実行されて完了します。

何がうまくいかなかったのか

MPI_Send はブロッキング関数になる可能性があります。つまり、 への呼び出しはMPI_Send、他のプロセスが一致する関数を呼び出すまで返されませMPI_Recv(ただし、ブロッキング関数である必要はありません)。コードを記述するときは、常にブロックされると想定する必要があります。

では、5 つ以上のプロセスで実行しているときに、0 以外のプロセスが何をするかを想像してみましょう。

  • プロセス 1 はその右隣 (プロセス 2) に送信し、プロセス 2 が を呼び出すまでそこで待機しますMPI_Recv
  • プロセス 2 はその右隣 (プロセス 3) に送信し、プロセス 3 が を呼び出すまでそこで待機しますMPI_Recv
  • プロセス 3 はその右隣 (プロセス 4) に送信し、プロセス 4 が を呼び出すまでそこで待機しますMPI_Recv
  • ...
  • プロセス n-2 は右隣 (プロセス n-1) に送信し、プロセス n-1 が呼び出すまでそこで待機します。MPI_Recv
  • プロセス n-1 には右側の隣接プロセスがないため、左側の隣接プロセスへの送信に進み、プロセス n-2 が を呼び出すまでそこで待機しますMPI_Recv

プロセス n-2 は、プロセス n-1 からデータを受信する前にプロセス n-1 がデータを受信するのを待っているため、これは決して起こりません。これはデッドロックであり、どちらのプロセスも動きません。

ソリューションが機能する理由

上記の解決策は私にとってはうまくいくと言いましたが、完全ではありません。私が行った唯一の変更は、プロセス 0 からの受信を最初のステップに移動することでした。なぜそれがデッドロックに影響したのでしょうか?

答えは、デッドロックにまったく影響を与えるべきではなかったということです。私の推測では、コンパイラは、各コアが同じネイバーに送受信していることを認識し、左と右のネイバーへの個別の呼び出しMPI_Sendと呼び出しを呼び出しに結合するのに十分なほど賢いです。これにより、同じステップでネイバーへの送受信が行われ、デッドロックの問題が解消されます。以前は、0 からの受信の呼び出しは、同じネイバーへの送信と受信の間にあったため、コンパイラはそれを単一の操作に最適化できませんでした。MPI_RecvMPI_Sendrecv

しかし、優れたコンパイラに依存したくはありません。コードは、標準に準拠した任意のコンパイラで動作する必要があります。そのため、デッドロックの問題を自分で手動で修正し、コンパイラが巧妙であることに依存しないようにする必要があります。

解決策 2

まず最初に、これまでのコースでカバーした可能性がある、またはカバーしていない可能性がある事柄についてコメントします。

  • プロセス 0 は、他のすべてのコアに同じ情報を送信しています。MPI_Bcastこれらすべての送受信の代わりにそれを使用する必要があることがわかっている場合。
  • プロセス 0 は、最後に他のすべてのコアから受信します。複数の char 配列を受け取りたい場合は、MPI_Gather.
  • マスタープロセスが他のすべてのプロセスにデータを送信し、各プロセスがその同じデータを各隣接プロセス (マスターによって既に与えられている) と共有するというロジックがよくわかりません。共有されたデータが何らかの形で異なっていた場合、またはマスタープロセスが一部のスレーブにのみデータを送信し、スレーブ間でデータを共有する必要があった場合は、より理にかなっています。

とはいえ、デッドロックを回避することについて話しましょう。したがって、根本的な問題は、1 つのプロセスが何をMPI_Send呼び出しても、別のプロセスが、送信プロセスが他の処理を行うのを待たずにMPI_Recv、一致するものを同時に呼び出すことができるようにする必要があることです。この問題は、各コアが同時に送信しようとすることから発生します。

したがって、これを修正できる 1 つの方法は、最初に情報が完全に一方向に移動することを決定することです。私は左から右を選択しました。その場合、各スレーブ コアは次のことを行う必要があります。

Receive_From_Master();

// Make sure all info is sent from left to right
Send_To_Right_Neighbour();
// Make sure any info is received from left to right
Receive_From_Left_Neighbour();

// Now send all info from right to left
Send_To_Left_Neighbour();
// Make sure any info is received 
Receive_From_Right_Neighbour();

Send_To_Master();

今起こっていることはこれです:

  • プロセス 2 はプロセス 3 への送信を開始します
  • プロセス 3 はプロセス 4 への送信を開始します
  • ...
  • プロセス n-2 がプロセス n-1 への送信を開始する
  • プロセス n-1 には適切な隣人がいないため、プロセス n-2 からの受信に進みます
  • プロセス n-2 がプロセス n-1 への送信を終了したため、プロセス n-3 からの受信に移ります
  • ...
  • プロセス 3 はプロセス 4 への送信を終了し、プロセス 2 からの受信に進みます。

左から右に送信する場合も同じことが起こりますが、プロセス 1 には送信先の左側の隣人がいないため、プロセス 2 からの受信に直接進むことができます。どちらの場合もデッドロックは発生しません。

于 2012-10-16T20:39:33.013 に答える