12

MPI を使用して行列ベクトル乗算プログラムを作成しようとしています。マトリックスの列を別々のプロセスに送信し、ローカルで結果を計算しようとしています。最後に、MPI_Reduce使用MPI_SUM操作を行います。

C は配列を行優先順に格納するため、行列の行の送信は簡単ですが、列はそうではありません (1 つずつ送信しない場合)。ここで質問を読みました:

MPI_Scatter - 2D 配列の列の送信

Jonathan Dursi は、新しい MPI データ型の使用を提案しました。以下は、彼のコードを自分のニーズに合わせて調整した結果です。

  double matrix[10][10];
  double mytype[10][10];
  int part_size; // stores how many cols a process needs to work on
  MPI_Datatype col, coltype;
  // ...
  MPI_Type_vector(N, 1, N, MPI_DOUBLE, &col);
  MPI_Type_commit(&col);
  MPI_Type_create_resized(col, 0, 1*sizeof(double), &coltype);
  MPI_Type_commit(&coltype);
  // ...
  MPI_Scatter(matrix, part_size, coltype,
              mypart, part_size, coltype,
              0, MPI_COMM_WORLD);

  // calculations...
  MPI_Reduce(local_result, global_result,
             N, MPI_DOUBLE,
             MPI_SUM,
             0, MPI_COMM_WORLD);

これは完全に機能しますが、その仕組みを本当に理解しているとは言えません。

  1. メモリにはどのようにMPI_Type_vector格納されますか?
  2. どのように機能MPI_Type_create_resized()し、正確には何をしますか?

私は MPI のまったくの初心者であることを心に留めておいてください。前もって感謝します。

4

1 に答える 1

39

この質問に対する私の回答には、この問題についての長い説明があります。多くの人がこれらの質問を持っているという事実は、それが明らかではなく、アイデアに慣れるのに時間がかかることを証明しています。

知っておくべき重要なことは、MPI データ型が記述するメモリ レイアウトです。への呼び出しシーケンスMPI_Type_vectorは次のとおりです。

int MPI_Type_vector(int count,
                   int blocklength,
                   int stride, 
                   MPI_Datatype old_type,
                   MPI_Datatype *newtype_p)

strideすべてのアイテム、blocklength引き出されたアイテムのブロック、およびcountこれらのブロックの合計があるメモリのレイアウトを記述する新しいタイプを作成します。ここのアイテムは、何の単位でもありold_typeます。たとえば、(ここでパラメーターに名前を付けて、実際には C では実行できませんが、:)

 MPI_Type_vector(count=3, blocklength=2, stride=5, old_type=MPI_INT, &newtype);

次にnewtype、メモリ内のレイアウトを次のように記述します。

   |<----->|  block length

   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
   | X | X |   |   |   | X | X |   |   |   | X | X |   |   |   |
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   |<---- stride ----->|

   count = 3

ここで、各正方形は 1 つの整数サイズのメモリ チャンクであり、おそらく 4 バイトです。ストライドは、ブロック間の距離ではなく、1 つのブロックの開始から次のブロックの開始までの整数の距離であることに注意してください。

わかりましたので、あなたの場合は電話しました

  MPI_Type_vector(N, 1, N, MPI_DOUBLE, &col);

count = Nこれは、それぞれサイズ s のブロックを取り、sblocklength=1 MPI_DOUBLEの各ブロックの先頭の間にスペースがありstride=N MPI_DOUBLEます。つまり、N 番目の double ごとに、合計 N 回かかります。(連続して格納された) NxN 倍精度配列から 1 列を抽出するのに最適です。便利なチェックは、どのくらいのデータがまたがっているか (count*stride = N*N行列のフル サイズ、チェック) と、実際に含まれているデータの量 ( count*blocksize = N、列のサイズ、チェック) を確認することです。

MPI_Send と MPI_Recv を呼び出して個々の列を交換するだけでよい場合は、完了です。このタイプを使用して列のレイアウトを記述できれば問題ありません。しかし、もう1つあります。

を呼び出しMPI_Scatterて、最初の coltype (たとえば) をプロセッサ 0 に送信し、次の coltype をプロセッサ 1 などに送信します。単純な 1d 配列でそれを行う場合、「次の」データ型がどこにあるかを簡単に把握できます。は; 各プロセッサに 1 つの int を分散している場合、最初の int が終了した直後に「次の」int が開始されます。

ただし、新しい coltype 列には、列の先頭からs までの合計範囲N*N MPI_DOUBLEがあります。MPI_Scatter が同じロジックに従う場合 (そうです)、マトリックス メモリの外側にある「次の」列を完全に探し始めます。次と次のように。必要な答えが得られないだけでなく、プログラムがクラッシュする可能性があります。

これを修正する方法は、「次の」データ型がどこにあるかを計算するためのこのデータ型の「サイズ」が、1 つの列の開始位置と次の列の開始位置の間のメモリ内のサイズであることを MPI に伝えることです。つまり、ちょうど 1 つMPI_DOUBLEです。これは、送信されるデータの量には影響しません。これは、依然として 1 列分のデータです。「次の行」の計算にのみ影響します。配列内の列 (または行) を使用して、このサイズをメモリ内の適切なステップ サイズに送信するだけで、MPI は送信する正しい次の列を選択します。このサイズ変更演算子がないと、プログラムがクラッシュする可能性があります。

上記にリンクされた 2D 配列の例の 2D ブロックのように、より複雑なデータ レイアウトがある場合、「次の」アイテム間に単一のステップ サイズはありません。サイズを有用な単位にするためにサイズ変更のトリックを行う必要がありますが、送信元の場所を明示的に指定するには、スキャッターではなくMPI_Scattervを使用する必要があります。

于 2012-05-28T17:24:26.123 に答える