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:正確にはこのプロセスでのみ, "受信"している.