02. MPI 版:領域を分けて解く

01. 逐次版 のプログラムを、\(P\) ランクで分担する heat1d_mpi に広げます。 領域分割とゴーストセルの交換の考え方は 第 10 章、MPI の関数は 第 8 章 で説明しています。

使うもの

逐次版のものに加えて、次を使います。

もの

ヘッダ

miso::Env(MPI_Init と MPI_Finalize)、miso::mpi::rank()、mpi::size()、mpi::comm()

miso/env.hpp

MPI_Sendrecv、MPI_Allreduce、MPI_Gather、MPI_Abort、MPI_PROC_NULL

mpi.h

main の最初で Env env(argc, argv); を作ると MPI が初期化され、main を抜けるときに終了処理が行われます。

作るもの

各ランクの格子

  • 全体のセル数 \(N\) をランク数 \(P\) で割り、各ランクの物理セル数を \(n = N/P\) とします。 \(N\) が \(P\) で割り切れないときは、ランク 0 が理由を表示し、MPI_Abort で全ランクを終了します。

  • 配列は Array1D<Real> u(n + 2) と u_new(n + 2) です。

  • ランク \(p\) の配列の添字 i(1〜n)は、全体の番号 \(j = p\,n + (i - 1)\) にあたります。 初期条件と誤差の計算では、この \(j\) から \(x_j = (j + 1/2)\Delta x\) を求めます。

  • \(\Delta x\)、\(n_\mathrm{steps}\)、\(\Delta t\)、\(r\) は全体の \(N\) から逐次版と同じ式で求めます。

隣のランク

  • 左隣は rank - 1、右隣は rank + 1 です。

  • ランク 0 の左隣と、ランク \(P - 1\) の右隣は MPI_PROC_NULL にします。

各ステップ

  1. 次の 2 回の MPI_Sendrecv でゴーストセルを埋めます。型は MPI_DOUBLE、要素数は 1、コミュニケータは mpi::comm() です。

    呼び出し

    送る値 → 相手

    受け取る場所 ← 相手

    タグ

    1 回目

    u[1] → 左隣

    u[n+1] ← 右隣

    0

    2 回目

    u[n] → 右隣

    u[0] ← 左隣

    1

    受信の状態は使わないので、最後の引数には MPI_STATUS_IGNORE を渡します。

  2. 左隣が MPI_PROC_NULL のランクは u[0] = -u[1] を、右隣が MPI_PROC_NULL のランクは u[n+1] = -u[n] を入れます。

  3. 逐次版と同じ式で、物理セル(Range1D{1, n + 1})を更新し、u と u_new を入れ替えます。

誤差と出力

  • 各ランクで reduce により自分の物理セルの最大誤差を求め、MPI_Allreduce(MPI_MAX)で全体の最大誤差にします。

  • MPI_Gather で各ランクの物理セル \(n\) 個(&u[1] から)をランク 0 に集めます。ランク 0 は \(N\) 要素の受信用の配列(std::vector<Real>)を用意します。

  • ランク 0 だけが、逐次版と同じ 3 行(1 行目は N = 64, P = 2, steps = 1024, r = 0.4000 のように \(P\) も表示)を表示し、 結果を result_N<N>_P<P>.txt(たとえば result_N64_P2.txt)に書き出します。

確かめること

逐次版を実行したのと同じフォルダで、次を実行します。

./build/heat1d_serial 64
for P in 1 2 4; do
  mpiexec -np $P ./build/heat1d_mpi 64
  cmp result_N64_serial.txt result_N64_P$P.txt
done
  1. 表示:\(P = 1, 2, 4\) のどれでも、表示される 2 つの誤差が逐次版と同じ値になります。

  2. ビット単位の一致:cmp が何も表示しない(ファイルが 1 バイトも違わない)ことを確かめます。

  3. 割り切れない分割:mpiexec -np 3 ./build/heat1d_mpi 64 で、理由を表示して終了することを確かめます。

  4. 3 ランク:\(N = 120\) で \(P = 1, 2, 3, 4\) を実行し、すべて逐次版の result_N120_serial.txt と一致することを確かめます。 このとき max error (exact) は 2.941414e-05 です。

逐次版と一致しないときは、次の順に調べます。

  • \(P = 1\) でも違うなら、初期条件の \(x_j\)(全体の番号 \(j\) の求め方)、\(\Delta t\) の求め方を調べます。

  • \(P = 1\) では一致し、\(P = 2\) で違うなら、MPI_Sendrecv の送る値・受け取る場所・相手・タグを、上の表と見比べます。

  • 端のセルだけ違うなら、物理境界の値を入れる条件(どのランクが入れるか)を調べます。

ファイルのどの行から違うかは、diff result_N64_serial.txt result_N64_P2.txt | head で分かります。 行番号から全体の番号 \(j\)(行番号 − 1)が分かり、どのランクの、どの端の値がおかしいかを絞り込めます。

次へ進む目安

複数のランク数で逐次版とビット単位で一致したら、03. GPU 版 へ進みます。 GPU がなくても、課題 3 の CPU 用ビルドまで進められます。