C++でOpenMPI入門2 サイズ, バリア, リダクション
リダクションを試してみる.
以下では各プロセスから各自のランクをリダクションで通信して, そのランクの平均値を求めている.
#include <iostream> #include <stdlib.h> #include <mpi.h> int main(int argc, char **argv) { MPI::Init(argc, argv); int rank = MPI::COMM_WORLD.Get_rank(); int size = MPI::COMM_WORLD.Get_size(); std::cout << "rank = " << rank << " (/" << size << ")" << std::endl; MPI::COMM_WORLD.Barrier(); int sum = 0; // MPI::COMM_WORLD.Allreduce(&rank, &sum, 1, MPI::INT, MPI::SUM); MPI::COMM_WORLD.Reduce(&rank, &sum, 1, MPI::INT, MPI::SUM, 0); if (rank == 0) { double mean = static_cast<double>(sum) / static_cast<double>(size); std::cout << "average = " << mean << std::endl; } MPI::Finalize(); }
新しくGet_sizeとBarrierとAllreduce, Reduceが登場.
Get_sizeはプロセスの総数を得てくるものです.
Barrierは一番簡単な同期の方法で, すべてのプロセスがBarrierをコールするまで待機します. このプログラムを実行して出力をみるとちゃんとBarrier出来ていないように見えるのだけれど, これはiostreamの出力の問題なのかな?
AllreduceとReduceはリダクションと呼ばれるもの. ここではMPI::INT型である各プロセスのrankの総和(MPI::SUM)を計算し, sumに代入する. Allreduceの1は送るデータの数を表している. MPI::SUM以外にもMPI::MAX, MPI::MIN, MPI::PRODなどがある. 最後のは"積".
Reduceでは引数の最後に0が入っているが, これは0番目のプロセスでのみsumを計算する*1ということ. だから, Reduceを使った場合は(rank != 0)のプロセスではsumは正しい値でない(初期値0のまま). 逆に言うと, Allreduceの場合はどのプロセスでも正しくsumを得ている.
実行してみた結果. 4プロセスの場合, ちゃんと"(0+1+2+3)/4"の結果が出力されているのがわかる.
$ mpirun -np 4 ./a.out rank = 0 (/4) average = 1.5 rank = 1 (/4) rank = 2 (/4) rank = 3 (/4)
もっと実用的な例として, モンテカルロで円周率の推定の並列版を挙げておく.
#include <iostream> #include <stdlib.h> #include <mpi.h> double myrand() { return static_cast<double>(rand()) / static_cast<double>(RAND_MAX); } double calc_pi(int seed, int trial) { srand(seed); int n = 0; for (int i = 0; i < trial; i++) { double x = myrand(); double y = myrand(); if (x * x + y * y < 1.0) { n++; } } return 4.0 * static_cast<double>(n) / static_cast<double>(trial); } int main(int argc, char **argv) { MPI::Init(argc, argv); int rank = MPI::COMM_WORLD.Get_rank(); int size = MPI::COMM_WORLD.Get_size(); double pi = calc_pi(rank, 1000000); std::cout << "rank = " << rank << " (/" << size << ") " << " : pi = " << pi << std::endl; MPI::COMM_WORLD.Barrier(); double sum = 0; MPI::COMM_WORLD.Allreduce(&pi, &sum, 1, MPI::DOUBLE, MPI::SUM); // MPI::COMM_WORLD.Reduce(&pi, &sum, 1, MPI::DOUBLE, MPI::SUM, 0); if (rank == 0) { sum = sum / static_cast<double>(size); std::cout << "average = " << sum << std::endl; } MPI::Finalize(); }
実行してみた結果.
$ mpirun -np 4 ./a.out rank = 0 (/4) : pi = 3.14166 rank = 1 (/4) : pi = 3.14166 rank = 2 (/4) : pi = 3.14382 average = 3.14191 rank = 3 (/4) : pi = 3.14048
*1:正確にはこのプロセスでのみ, "受信"している.