【C++】OpenMPで並列処理をやってみる【macOS Mojave 10.14】
OpenMP
OpenMPはC/C++用の並列計算を簡単に実装できるライブラリ.
有名らしいけど,今回初めて使用してみたのでその備忘録として色々まとめます.
ちなみにタイトルの通り,macOS Mojave 10.14.6での実装.
下準備
まずMacOSでOpenMPを利用するには,Clangではなくgcc/g++にコンパイラを切り替える必要があるようだ.
早速インストールを行う.
1 | $ brew install gcc |
ここですでにインストール済みであればせっかくなのでアップデートもしておくと良いでしょう.
そうしたら切り替えるわけだが,まずgccの場所を確認する.
1 | $ which gcc |
そのあと,先ほどインストールされたgccとg++は,おそらく「/usr/local/bin/」にあるハズ.
一応確認する.
1 | $ ls /usr/local/bin | grep gcc |
そうしたら,先ほどのgccの場所に対して,新しいgccをldコマンドを用いてリロケートする.
もし,gccの場所が「/usr/bin/」であれば,
1 2 | $ sudo ln -sf /usr/local/bin/gcc-9 /usr/bin/gcc $ sudo ln -sf /usr/local/bin/g++-9 /usr/bin/g++ |
のようにする.(このとき gcc-9 と g++-9 はバージョンによって異なる)
Operation not permitted
MacOSのバージョンによってはこいつが出てくる.
実際に僕も遭遇しました.
そういう人は以下のページを参考にしてSIP(System Integrity Protection)を無効にして試してみてください.
参考:EI Capitanでsudo付けているOperation not permittedが出た時の対処法
CLionなどのIDEを用いる場合
もしかしたらIDEの参照しているコンパイラがまだ過去のものかもしれない.
CLionであれば以下を参照してください.
CMakeLists.txtに書くこと
もしCMakeを使う人は参考までに.
なお,このあとの実装はCMakeを用いている.
CMakeLists.txtには最低でも以下の記述が追加で必要になります.
1 2 3 4 | find_package(OpenMP REQUIRED) set(CMAKE_C_FLAGS "\${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") set(CMAKE_CXX_FLAGS "\${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") |
以下はCMakeLists.txt全体の一例.(本記事のCMakeLists.txt)
1 2 3 4 5 6 7 8 9 10 11 | cmake_minimum_required(VERSION 3.14) # make 推奨の最低バージョン project(omp_test) #project名 set(CMAKE_CXX_STANDARD 14) # c++14 find_package(OpenMP REQUIRED) set(CMAKE_C_FLAGS "\${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") set(CMAKE_CXX_FLAGS "\${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") add_executable(omp_test main.cpp) |
ちなみに普通にターミナルからコンパルするときは,-fopenmpオプションをつける.
OpenMPを使ってみる
下準備が終わったので本題に入ります.(結構下準備に時間がかかったなんて言えない)
まずは超シンプルなコードから.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include <iostream> #include <omp.h> using namespace std; int main() { int threads_num = omp_get_max_threads(); cout << "All Threads num: " << threads_num << endl; #pragma omp parallel for for( int i = 0; i < 10; ++i ){ // 並列する処理 cout << "Thread No. " << omp_get_thread_num() << endl; } return 0; } |
なにもしないプログラムだが,まあOpenMPの動作確認としては十分.
1 2 3 4 5 6 7 8 9 10 11 | All Threads num: 8 Thread No. Thread No. 5 Thread No. Thread No. Thread No. Thread No. 3 6 4 7 2Thread No. Thread No. 1 0 Thread No. 0 Thread No. 1 |
入り乱れている.笑
おそらく,あるスレッドが標準出力処理をしているときに,改行( endl )する前に他のスレッドの同じ処理が被ってしまっただけだと思う.
クリティカルセクションの指定
クリティカルセクションとは,ある処理を並列処理しているときに,同時に実行しない領域を指す.
OpenMPでもサポートされているようです.
先ほどのコードを以下のように加筆しました.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #include <iostream> #include <omp.h> using namespace std; int main() { int threads_num = omp_get_max_threads(); cout << "All Threads num: " << threads_num << endl; #pragma omp parallel for for( int i = 0; i < 10; ++i ){ // 並列する処理 #pragma omp critical cout << "Thread No. " << omp_get_thread_num() << endl; } return 0; } |
そうすると,
1 2 3 4 5 6 7 8 9 10 | Thread No. 7 Thread No. 3 Thread No. 2 Thread No. 4 Thread No. 1 Thread No. 1 Thread No. 0 Thread No. 0 Thread No. 6 Thread No. 5 |
入り乱れることはなくなりました.
このクリティカルセクションは上手く使わないと,予想外の結果が返ってくる可能性がある.
例えば,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #include <iostream> #include <vector> #include <omp.h> using namespace std; int main() { int threads_num = omp_get_max_threads(); cout << "All Threads num: " << threads_num << endl; int sum = 0; vector<int> vec(100, 0); for( int i = 0; i < 100; ++i ){ vec[i] = i; } #pragma omp parallel for for(int i = 0; i < 100; ++i ){ sum += vec[i]; } cout << "sum: " << sum << endl; return 0; } |
を実行すると,実行の度に違う結果が返ってくる.
そこで,
1 2 3 4 5 | #pragma omp parallel for for(int i = 0; i < 100; ++i ){ #pragma omp critical sum += vec[i]; } |
とするだけで,おかしな結果が返ってくることはなくなる.
reduction
他にも競合を解消する方法は,reduction指示節を記述する方法がある.
記法は reduction({演算子}: {変数名}) で,スレッド間で共有したい変数をあらかじめ宣言しておくというもの.
例えば先ほどのコードは以下のように書いても期待通りの結果が得られる.
1 2 3 4 | #pragma omp parallel for reduction(+:sum) for(int i = 0; i < 100; ++i ){ sum += vec[i]; } |
こっちの方がスマートなのかもしれない.
イテレーションループは使えない
使っていて気づいたが, for( auto v : vec ) のようなイテレータを使ったループは並列化できない.
これは,OpenMPが何回ループがあるか事前にわからないから,並列しようがない,ということらしい.
まあ確かにそうだ.笑
おわりに
簡単にではありますが,OpenMPについて記事を書きました.
とりあえず使えそうな情報だけ書きました.
ほとんど備忘録のようなものですが,誰かの興味に刺さったり,困っている人の手助けになれば幸いです.