今回やること
MPIのサンプルコードの紹介, 及び MPIによるプロセス並列の組み込み.
MPI
MPIとは, プロセス並列のための通信規格です. この規格を実際に実装しているものとして openMPI, MVAPICH, Intel MPI などのライブラリがあります. プロセス並列なので, それぞれのworker(並列単位)はお互いに異なるメモリ空間を持ち, データを共有するためにはメッセージパッシングを用いて行う必要があります.
通信規格( 関数名や引数 )は統一されているため, 用いるライブラリ(openMPI, MVAPICH, Intel MPI)を変えたとしても プログラムは変更する必要はありません. (ありがたい)
install
今回はopenMPIを使ってみます.
ubuntuであれば
$ apt install -y libopenmpi-dev
で入ります.
超簡単なサンプル実装
上記をsample_mpi.cppとして保存して,
$ mpicxx sample_mpi.cpp
でコンパイルします. 実行は
$ mpirun -n 3 ./a.out This worker is 2 of 3 This worker is 0 of 3 This worker is 1 of 3
3つのworkerがそれぞれ出力を行っていますね.
データ集約(MPI_Gather)の例
もう少し複雑な例を紹介します. それぞれのプロセスでデータを計算して, それをどこか一つのrankに集約する メッセージパッシングを書くとこうなります.
sbufは送信データを格納するバッファ, rbufは受信データを格納するバッファで rank = 0 がデータを受け取るので, rank = 0のみメモリ領域を確保するようにしています.
$ mpirun -n 4 ./a.out rank 0 send 0 1 rank 3 send 6 7 rank 2 send 4 5 rank 1 send 2 3 rbuf 0 1 2 3 4 5 6 7
rank = 0 のrbufにデータが集まっているのが分かりますね.
逐次的なデータ送信の例
次は少し複雑に次のような状況を実現してみます.
- (rank > 0)はMPI_Isendで前回送信時から新しく行ったジョブ数をrank=0にtag=0で送信します. rank=0が受け取る前に送信バッファが変更されないようにdequeに送信する情報を格納してから送ります.
- (rank = 0)はMPI_Recvでそれを受け取り, progress barを更新します.
- (rank > 0)はMPI_Testで送信した情報をrank=0が受け取っているのかを確認します. 受け取り済みのものはdequeから削除します.
- (rank > 0)は全てのジョブを行ったら, tag=1で0バイトのメッセージを送ります. ( 終了したことを伝えるためだけのもの )
- (rank = 0)は全てのrank > 0 のworkerから終了メッセージを受け取ったのを確認してから終了します.
$ mpicxx mpi_progress.cpp -std=c++11 -I./source/progress-cpp/include
実行
$ mpirun -n 3 ./a.out
MPIの組み込み
CMake
オプションを追加, オプションがONのときに
- find_package(MPI)
- CMAKE_CXX_COMPILER を MPI_CXX_COMPILER に変更 ( もっといい書き方があるかも )
option(USE_MPI "Use MPI" OFF) if(USE_MPI) find_package(MPI REQUIRED) set(CMAKE_CXX_COMPILER ${MPI_CXX_COMPILER}) # Maybe there's a better way... message("Set CMAKE_CXX_COMPILER <-- ${CMAKE_CXX_COMPILER}") endif()
Dockerfile
追加
RUN apt-get libopenmpi-dev
run.cpp
MPI並列用のtest関数. MPI_Gatherで, それぞれのrankのデータをrank=0に収集して, 統計情報を出力します. くわしはこちらを参照 ( 差分 )
void runTestPara( Config &config, int nProcesses, int rank ) noexcept { CodePtrList S; allCodeGenerator(config, S); std::vector<int> countTable(S.size()); std::vector<double> timeTable(S.size()); // initialize the bar progresscpp::ProgressBar progressBar(S.size(), 70); for ( int i = 0; i*nProcesses + rank < static_cast<int>(S.size()); ++i ) { if ( rank == 0 ) { // 簡易的なprogress bar for ( int j = 0; j < nProcesses; j++ ) ++progressBar; progressBar.display(); } // test code // (省略) assert( *testS[0] == *secret ); double elapsed = end - start; countTable[i] = count; timeTable[i] = elapsed; } if( nProcesses > 0 ) { // データ集め MPI_Gather(countTable.data(), sendcount, MPI_INT, countTable.data(), sendcount, MPI_INT, 0, MPI_COMM_WORLD); MPI_Gather(timeTable.data(), sendcount, MPI_DOUBLE, timeTable.data(), sendcount, MPI_DOUBLE, 0, MPI_COMM_WORLD); } if( rank == 0 ) { auto endTotal = omp_get_wtime(); // output statistics // (省略) } }
Github-action (CMake)
CMake用のgithub actionに下記を追加. そのままだとCMakeの最中に
Could NOT find MPI (missing: MPI_CXX_FOUND)
なので, 単純に
sudo apt install -y openmpi-bin libopenmpi-dev
をCMake前に実行すればok.
- name: Build OpenMPI run: sudo apt install -y openmpi-bin libopenmpi-dev - name: Configure CMake with MPI run: | rm -rf ${{github.workspace}}/build/* # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DUSE_MPI=ON - name: Build with MPI # Build your program with the given configuration run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - name: Test with MPI working-directory: ${{github.workspace}}/build # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest -C ${{env.BUILD_TYPE}}
実験
$ OMP_NUM_THREADS=1 ./bin/mastermind ${num_colors} ${num_pins} --test --policy entropy
にて, 並列数ごとの total_timeを比較します. それぞれ10回実行して, 平均時間を算出します.
machine A
num colors | num pins | total time[s] processes=1 |
total time[s] processes=4 |
total time[s] processes=8 |
total time[s] processes=16 |
---|---|---|---|---|---|
4 | 4 | 0.458 | 0.179 | 0.107 | 0.064 |
5 | 4 | 3.009 | 1.027 | 0.565 | 0.330 |
6 | 4 | 24.444 | 6.876 | 3.937 | 2.089 |
3 | 5 | 0.331 | 0.136 | 0.085 | 0.052 |
4 | 5 | 14.098 | 4.014 | 2.317 | 1.308 |
5 | 5 | 203.150 | 55.562 | 31.100 | 15.789 |
こちらも順調に高速化されていますね.
まとめ
コード
参考
他の記事
- 次の記事 この記事が最終回の可能性が高し.
- 前の記事 inarizuuuushi.hatenablog.com
- 一覧 mastermind カテゴリーの記事一覧 - サブロウ丸