UTALI

書き溜めておいた技術記事や旅行記のバックアップです。

OpenMP で かんたん 並列プログラミング を始めよう - C言語・C++

f:id:mochizuki_p:20161221101748p:plain

本来C言語やC++で並列プログラミングを実装するのは非常に高度なプログラミングスキルを必要とする敷居の高いタスクでした。

OpenMPはC/C++での並列プログラミングをお手軽に実装できる便利な機能です。

それはコンパイラの組み込み機能として定義され、プログラム中にOpenMPの機能として定義された任意のマクロを埋め込むことで簡単に並列化を実装できるのです。

今回は一番並列化の効果が高いと思われるfor文の並列化とそのパフォーマンス向上効果について、簡単なベンチマークを行ってみたいと思います。

for文の並列化

for文は大抵の機械学習のタスクにおいて計算のボトルネックとなる箇所です。

プログラミングの世界では、パレートの法則と呼ばれる格言があります。それはプログラミングの実行時間の80%はソースコード中のわずか20%の部分で消費されるということです。

ここを並列化することで、計算時間の大幅な削減を期待できます。

pragma omp parallel

C/C++で#pragma omp parallelという記述を追加すると、次の1文またはブロックの部分が並列化されます。

これをfor文で適用するにはさらにforを追加します。

#pragma omp parallel for
for(i=0;i<100;i++){
   test[i]=0; 
}

for文の並列化 はさらに詳細なオプションを追加することが可能です。

schedule

for文の並列化のオプションとしてスケジュールを追加することが可能です。

スケジュールのオプションは主に3種類で

  • static - 各スレッドに等しく計算量を配分する

  • dynamic - 指定したサイズごとに開いてるスレッドに計算量を割り当てる。計算量の配分のための余計なオーバヘッドがかかる

  • guided - 最初は大きなチャンクを割り当てる。順番に小さくしていく。

  • auto - コンパイラが自動的に最適化する

ベンチマーク

今回使用するのは以下のプログラムです。100万回のループです。

open_test.c

#include<math.h>
#include<stdlib.h>

int main(int argc,char** argv){

    double p[1000000];

    #pragma omp parallel for schedule(guided)
    for(long i = 0; i < 1000000;i++){
        p[i] = log(rand())/10.0;
    }

    return 0;
}

コンパイル

OpenMPを有効化するのは簡単で、コンパイル時のオプションとして-fopenmpを追加するだけでOKです。OpenMPによる恩恵を最大化するには-O3の最適化を有効にするのがベストでしょう。

gccでのコンパイルは以下のようにするとよいでしょう。

gcc open_test.c -o open_test -fopenmp -O3

ちなみは環境は、

  • 3コア 1.6 GHz Intel Core i5
  • RAM 4GB

です。

結果

time ./open_test

で計測して以下の結果を得ました。

OpenMPを利用しない場合

  • real 0m0.027s
  • user 0m0.019s
  • sys 0m0.006s

dynamicの場合

  • real 0m0.060s
  • user 0m0.179s
  • sys 0m0.027s

staticの場合

  • real 0m0.020s
  • user 0m0.045s
  • sys 0m0.007s

guidedの場合

  • real 0m0.020s
  • user 0m0.047s
  • sys 0m0.007s

autoの場合

  • real 0m0.023s
  • user 0m0.046s
  • sys 0m0.008s

まとめ

OpenMP forのオプション 実行時間(秒)
なし 0.027s
あり static 0.020s
あり dynamic 0.060s
あり guided 0.020s
あり auto 0.023s

となり、OpenMPによる並列化で計算時間の削減ができることがわかります。

問題はdynamicで、OpenMPを利用しない場合よりも時間がかかってしまいました。これは計算量の配分のコストによるものでしょうか?

マクロを活用する

OpenMPを有効化すると、マクロ _OPENMPが定義されます。これを活用して以下のような記述を行うと便利です。

#include<math.h>
#include<stdlib.h>

int main(int argc,char** argv){

    double p[1000000];

    #ifdef _OPENMP
    #pragma omp parallel for schedule(static)
    #endif
    for(long i = 0; i < 1000000;i++){
        p[i] = log(rand())/10.0;
    }

    return 0;
}

これはOpenMPの機能を有効化している時だけ、#pragma omp parallel for schedule(static)を展開しろ、という意味です。