今回やること
openMPを用いた並列化
openMP
言わずと知れた, スレッド並列ツール. FortranとC/C++に対応.
特徴的なのが, (openMPがうまく動くようなコードになっていれば)並列化のために手を加える箇所が少ないこと.
またスレッド並列なので, プロセス間のメッセージパッシングによるデータ共有を行う必要もありません.
要するにopenMPにて比較的お手軽に並列化を行うことができます.
逆に気をつけなければならないのがデータ同期, 全てのスレッドが同じメモリ空間を共有するため, グローバルデータの書き換え時に注意する必要があります.
並列化
最も簡単なのは並列化したいforループにopenMPを適用することです. 上記の図のようにコード一覧を分割して, 並行してそのコードに対する推論アルゴリズムの性能を測定します.
下記のように, forループの前に#pragma omp parallel for
を追加します.
これにより for ループがスレッド数分に分割されます. スレッド数は環境変数 ( OMP_NUM_THREADS ) が使用されます. ( 他の指定方法もあります)
ループ内で使用している変数は(デフォルトでは)全てのスレッドで共有扱いになるので,
スレッドごとに独立させたい変数については, private
や firstprivate
により追加で指定します.
( OpenMP入門: データ共有属性の指定について )
この1行を追加するだけ.
/** * @fn void runInteractive(Config &config) * @brief 全てのsecret codeに対する計測 * @param[in] config */ void runTest( Config &config ) noexcept { CodePtrList S; allCodeGenerator(config, S); std::vector<int> countTable(S.size()); std::vector<double> timeTable(S.size()); auto startTotal = omp_get_wtime(); #pragma omp parallel for firstprivate(config) // ここだけ追加! for ( int i = 0; i < static_cast<int>(S.size()); ++i ) { // test code config.setSecret(*S[i]); CodePtrList testS = copy(S); CodeList guessHist; CodePtrList G; int count = 0; auto start = omp_get_wtime(); while( testS.size() > 1 ) { count++; G.clear(); setGuessCandidates(S, guessHist, config, G); Code guess = policy(testS, G, config); trial(testS, guess, config); guessHist.push_back(guess); } double elapsed = omp_get_wtime() - start; countTable[i] = count; timeTable[i] = elapsed; } auto elapsedTotal = omp_get_wtime() - startTotal; }
namespace内に用意しているグローバル変数で固定値でないものは thread_local
修飾子をつけて
, スレッドごとに用意されるようにします.
extern thread_local std::vector<int> x; // もともと extern std::vector<int> x; extern thread_local std::vector<int> y; // もともと extern std::vector<int> y;
thread_local std::vector<int> x(0, 0); // もともとstd::vector<int> x(0, 0); thread_local std::vector<int> y(0, 0); // もともとstd::vector<int> y(0, 0);
これで目的の並列化は達成できます.
CMake
find_package
find_package(OpenMP REQUIRED) set(OpenMPLib OpenMP::OpenMP_CXX)
target_compile_options
-fopenmp
を追加. ただし mac (AppleClang)の場合は -Xclang -fopenmp
とXclangも必要でした.
target_compile_definitions
target_compile_definitions( master_mind_lib PRIVATE ${OpenMp_CXX_DEFS} )
実験
$ OMP_NUM_THREADS=${num_threads} ./bin/mastermind ${num_colors} ${num_pins} --test --policy entropy
にて, 並列数ごとの total_timeを比較します. それぞれ10回実行して, 平均時間を算出します.
mac book
num colors | num pins | total_time[s] threads=1 |
total_time[s] threads=2 |
total_time[s] threads=4 |
---|---|---|---|---|
4 | 4 | 0.4374 | 0.2296 | 0.1290 |
5 | 5 | 242.9154 | 131.7104 | 97.5318 |
5 | 4 | 3.4860 | 2.2360 | 1.0285 |
6 | 4 | 31.6422 | 18.7869 | 11.9237 |
4 | 5 | 15.2080 | 8.3673 | 5.9889 |
3 | 5 | 0.3130 | 0.1628 | 0.1014 |
machine A
num colors | num pins | total time[s] threads=1 |
total time[s] threads=2 |
total time[s] threads=4 |
total time[s] threads=8 |
total time[s] threads=16 |
---|---|---|---|---|---|---|
4 | 4 | 0.4684 | 0.2556 | 0.1430 | 0.0842 | 0.0442 |
5 | 5 | 204.3259 | 105.8567 | 56.4360 | 30.7383 | 16.7211 |
5 | 4 | 2.9121 | 1.6415 | 0.8832 | 0.4671 | 0.2673 |
6 | 4 | 24.4358 | 13.0411 | 7.1945 | 3.8827 | 2.1821 |
4 | 5 | 14.1828 | 7.3915 | 3.8774 | 2.2165 | 1.1551 |
3 | 5 | 0.3103 | 0.1937 | 0.0973 | 0.0521 | 0.0284 |
順調に高速化されていますね. ぱっと見, 線形的に実行時間が減少しているのがみて取れます.
まとめ
- openMPによりスレッド並列を実装しました.
- スレッド数により, --test 実行が線形的に高速化されました,
コード
参考
他の記事
- 次の記事
- 前の記事
- 一覧 mastermind カテゴリーの記事一覧 - サブロウ丸