初めてOpenMPを使ってみて気づいたこと

最近、あるプログラムを並列化して速度を上げるためにOpenMPを使っています。ネット上で検索するとOpenMPの入門記事はあっても実際に使ってみたよ、というのは多くないようなのでそのときに気づいたことなどを書いてみました。

OpenMPと他の並列化技術との比較

並列化技術はOpenMPに限らず色々あります。C++なら最近は標準でstd::threadやstd::asyncがあります。また、並列化にはマルチスレッドによる並列化以外にもSIMDGPGPUや分散コンピューティングによる並列化もあります。OpenMPはもともとマルチスレッドによる並列化技術でしたが、最近の規格ではSIMDGPGPUによる並列化も定義していて、コンパイラは対応を進めている最中のようです。今回はOpenMPのマルチスレッドによる並列化を行いました。

速度面に関してはgccベンチマーク(-O3 -march=native)を取ってみたところ、OpenMPとstd::threadやstd::asyncではほぼ変わりないようです。

#pragmaによる記述は好みが分かれそうなところですが、既存のコードへの変更が少なくて済むのが利点です。特に並列for構文では各スレッドへのタスクの割り当てと結果のreductionを自動化してくれるので、プログラマが部分タスクへの切り分けをしなくて良くなります。また、上手く書けばOpenMPを無効化しても意味を変えずにコンパイルできるのも特徴です。

仕様書が読みやすい

OpenMP仕様書はすごく読みやすいので迷ったら当たってみることをお勧めします。最新の規格はOpenMP4.0ですが、一つ前のOpenMP3.0の仕様書は日本語版も公開されています。
http://openmp.org/wp/openmp-specifications/

初期値の設定

OpenMPでは多くのパラメータの初期値が実装定義となっているので、特定の値が欲しければ自分で設定する必要があります。並列化するスレッド数が実装定義とか言われても困るので

omp_set_num_threads(omp_get_num_procs());

などで設定しましょう。

スレッド数の動的管理の設定は重要そうですが私の環境では目に見える違いはありませんでした・・・

omp_set_dynamic(1);

scheduleの設定は重要

scheduleは並列for構文で並列化された処理をどのようにスレッドに割り当てるかを設定します。大まかに言えばループごとの処理量が一定ならschedule(static)が速く、そうでないならschedule(dynamic)などのほうが速いようです。場合によってはかなり差が出るので実際に試してみた方がいいところだと思います。ちなみに何も書かなかった場合の挙動は例によって実装定義です。

orderedは厄介

orderedは並列for構文内で一部だけ本来の順序で実行する機能です。そもそも並列化して実行速度を稼いでいるところに本来の順序を持ち込むので、当然速度は下がります。それだけではなくて、実行のブロックが入るせいかschedule次第で速くなったり遅くなったりフリーズしたりします。この辺はもしかしたらコンパイラごとに状況が違うのかもしれません。入力や出力を複数用意するなどの方法で避けられるなら避けたたほうが良さそうです。