0

最初と最後の要素を一定に保ちながら、配列内の各要素をそれ自体とその隣接要素 (前のタイムステップ) の平均値に繰り返し設定する MPI プログラムを実装しようとしています。1 つのプロセスでは、これで問題なく動作します。ただし、複数のプロセスの場合、正しい答えが得られず、特に最初の配列要素が常に上書きされます。

私の初期化ステップは、少なくとも「計算前」の出力に関する限り、正しく機能しているようです。これは、使用されるプロセスの数が1以上であるかどうかに関係なく、同じベクトルを出力します。

完全に確信が持てないことの 1 つは、MPI_Request と MPI_Status を正しく使用しているかどうかです。注意すべき変数はsendLsendR、およびstatusです。

コードの関連部分のみを含めようとしました。「...」は、何かが欠けていることを示します。これらの省略記号の一部には、削除された内容を説明するコメントが付いています。並列実装と単一プロセス実装の両方が比較のために示されています。

...
#include "mpi.h"

... //definition of f() for initialization

int main(int argc, char **argv) {
  int         id, p, i, j, k, n, t, m, v, vp,
              lbound, ubound, block_size, offset;
  double      startwtime, endwtime;
  float       time;
  MPI_Request *sendL, *sendR;
  MPI_Status  *status;  /* return status for receive */
  double      *prev, *cur, *temp;

  ... // initialize MPI; get PE rank and size

  .... // set the following:
       // n = vector length, m = num iterations, k = buffer size
       // v = verbose (true/false)

  // Memory allocation for output from MPI functions
  // Note that I never actually initialized these. Is this a problem?
  sendL = (MPI_Request *) malloc(sizeof(MPI_Request));
  sendR = (MPI_Request *) malloc(sizeof(MPI_Request));
  status = (MPI_Status *) malloc(sizeof(MPI_Status));
  // Memory allocation for data array.
  block_size = (n/p+2*k);
  prev  = (double *) malloc( sizeof(double) * block_size);
  cur   = (double *) malloc( sizeof(double) * block_size);

  ... //malloc error handling

  t = 0;
  /* The following block is for a single process. It works correctly. */
  if(p==1){
     // Initialization
     startwtime = MPI_Wtime();
     for(i=0;i<n;i++)  prev[i] = f(i,n);
     cur[0] = f(0,n); cur[n-1] = f(n-1,n);
     if(v){
       printf("Before calculation\n");
       for(i=0;i<n;i++) printf("%f ",prev[i]);
       printf("\n");
     }
     while (t < m) {
      for ( i=1 ; i < n-1 ; i++ ) {
            cur[i] = (prev[i-1]+prev[i]+prev[i+1])/3;
       }
      temp = prev; prev = cur;  cur  = temp; t++;
      }
     if(v){
       printf("After calculation:\n");
       for(i=0;i<n;i++) printf("%f ",prev[i]);
       printf("\n");
     }
     endwtime = MPI_Wtime();
     time = endwtime-startwtime;
     printf("Sequential process complete, time: %f\n", time);
     return MPI_Finalize();     
  }
  /* Here is my parallel implementation. It has problems. */
  else{
     if (id == 0){
         startwtime = MPI_Wtime();
     }
     // Initialization
     offset = id*(n/p)-k;
     for(i=0;i<block_size;i++)  prev[i] = f(i+offset,n);
     cur[0] = f(0,n); cur[block_size-1] = prev[block_size-1];
     if (id == 0){
         for (i=0;i<k;i++){
            prev[i] = f(0,n);
            cur[i] = prev[i];
         }
     }
     if (id == p-1){
         for (i=block_size-k;i<block_size;i++){
             prev[i] = f(n-1,n);
             cur[i] = prev[i];
         }
     }
     if(v && id == 0){
       printf("Before calculation:\n");
       for(j=k;j<(n/p)+k;j++) printf("%f ",prev[j]);
       for(i=1;i<p;i++){
         MPI_Recv(prev+k,(n/p),MPI_DOUBLE_PRECISION,i,2,MPI_COMM_WORLD,status);
         for(j=k;j<(n/p)+k;j++) printf("%f ",prev[j]);
       }
       printf("\n");
     }
     else if (v){
       MPI_Isend(prev+k,(n/p),MPI_DOUBLE_PRECISION,0,2,MPI_COMM_WORLD,sendL);
     }
     lbound = (id == 0) ? (k+1) : (1);
     ubound = (id == p-1) ? (block_size-k-2) : (block_size-2);
     while (t < m) {
        for ( i=lbound ; i < ubound ; i++ ) {
              cur[i] = (prev[i-1]+prev[i]+prev[i+1])/3;
         }
        temp = prev; prev = cur;  cur  = temp; t++;
        if (t%k == 0){
          if (id > 0){
           // send to left
           MPI_Isend(prev+k,k,MPI_DOUBLE_PRECISION,id-1,0,MPI_COMM_WORLD,sendL);
          }
          if (id < p-1) {
           // send to right
           MPI_Isend(prev+block_size-2*k,k,
                   MPI_DOUBLE_PRECISION,id+1,1,MPI_COMM_WORLD,sendR);
          }
          if (id < p-1){
           // receive from right
           MPI_Recv(prev+block_size-k,k,
                   MPI_DOUBLE_PRECISION,id+1,0,MPI_COMM_WORLD,status);
          }
          if (id > 0) {
           // receive from left
           MPI_Recv(prev,k,MPI_DOUBLE_PRECISION,id-1,1,MPI_COMM_WORLD,status);
          }
        }
      }
     if(v && id == 0){
       printf("After calculation\n");
       for(j=k;j<(n/p)+k;j++) printf("%f ",prev[j]);
       for(i=1;i<p;i++){
         MPI_Recv(prev+k,(n/p),MPI_DOUBLE_PRECISION,i,2,MPI_COMM_WORLD,status);
         for(j=k;j<(n/p)+k;j++) printf("%f ",prev[j]);
       }
       printf("\n");
     }
     else if (v){
       MPI_Isend(prev+k,(n/p),MPI_DOUBLE_PRECISION,0,2,MPI_COMM_WORLD,sendL);
     }
     if (id == 0){
        endwtime = MPI_Wtime();
        time = endwtime-startwtime;
        printf("Process 0 complete, time: %f\n", time);
     }
     return MPI_Finalize();     
  }
}
4

2 に答える 2

2

まず最初に。コードのこの部分は非常に複雑です。

MPI_Request *sendL, *sendR;
MPI_Status  *status;  /* return status for receive */

sendL = (MPI_Request *) malloc(sizeof(MPI_Request));
sendR = (MPI_Request *) malloc(sizeof(MPI_Request));
status = (MPI_Status *) malloc(sizeof(MPI_Status));

MPIのハンドルは、整数やポインターなどの単純な型です。この場合、動的割り当ては意味がありません。ステータスも3〜4フィールドの単純な構造であり、ヒープに割り当てる意味はありません。代わりにスタック変数を使用してください。

MPI_Request sendL, sendR;
MPI_Status status;

別の問題もあります。非ブロッキング送信を開始しますが、それらの完了を保証することはありません。つまり、呼び出しMPI_WaitたりMPI_Test、要求ハンドルを使用したりすることはありません。それらは実際には完了まで進まない可能性があり、受信コードのデッドロックにつながる可能性があります。実際には、これらの非ブロッキング呼び出しはまったく必要ありませんが、 /MPI_Sendrecvの組み合わせを使用する目的で特別に設計されたを使用します。次のコード:MPI_IsendMPI_Recv

if (id > 0){
   // send to left
   MPI_Isend(prev+k,k,MPI_DOUBLE_PRECISION,id-1,0,MPI_COMM_WORLD,sendL);
}
if (id < p-1) {
   // send to right
   MPI_Isend(prev+block_size-2*k,k,
             MPI_DOUBLE_PRECISION,id+1,1,MPI_COMM_WORLD,sendR);
}
if (id < p-1){
   // receive from right
   MPI_Recv(prev+block_size-k,k,
            MPI_DOUBLE_PRECISION,id+1,0,MPI_COMM_WORLD,status);
}
if (id > 0) {
   // receive from left
   MPI_Recv(prev,k,MPI_DOUBLE_PRECISION,id-1,1,MPI_COMM_WORLD,status);
}

次のように置き換えることができます:

int prev_rank, next_rank;

prev_rank = (id > 0) ? id-1 : MPI_PROC_NULL;
next_rank = (id < p-1) ? id+1 : MPI_PROC_NULL;

...

MPI_Sendrecv(prev+k, k, MPI_DOUBLE, prev_rank, 0,
             prev+block_size-k, k, MPI_DOUBLE, next_rank, 0, MPI_COMM_WORLD, &status);
MPI_Sendrecv(prev+block_size-2*k, k, MPI_DOUBLE, next_rank, 1,
             prev, k, MPI_DOUBLE, prev_rank, 1, MPI_COMM_WORLD, &status);

ランクチェックは、nullプロセス、つまりランクが付いたプロセスの概念を使用して削除されますMPI_PROC_NULL。これはMPIの非常に特別なランクです。いつでもメッセージを送受信でき、これらの操作は単純に操作なしです。正しいMPIデータ型は。であることに注意してくださいMPI_DOUBLEMPI_DOUBLE_PRECISIONFortranデータ型用DOUBLE PRECISIONです。はブロッキング呼び出しであるためMPI_Sendrecv、各呼び出しは、デッドロックを防ぐために、前のプロセスからデータを受信しながら次のプロセスにデータを送信するように書き込まれます。

于 2012-12-07T20:47:14.147 に答える
0

「計算前」の出力はprevポインタを上書きします。おっとっと。

于 2012-12-07T20:05:20.887 に答える