OpenMP にて使用するスレッド数を指定する2つの方法とその違い

確認した環境

  • Windows10
  • Visual Studio Community 2015 Update 3
  • CPU Intel(R) Core(TM) i7-4790
    • コア数4、論理プロセッサ数8

使用するスレッド数を指定する2つの方法

forループ内の処理を並列で処理したい場合、 OpenMP が有効になっていれば#pragma omp parallel forをfor文の直前に加えることで並列処理になります。

#pragma omp parallel for
for (int i = 0; i < N; ++i) {
    hoge();
}

このときの並列処理を実行するスレッド数は、omp_get_max_threads関数で得られる値です。
使用するスレッド数を指定したい場合、方法は2つあります。
一つはomp_set_num_threads関数を使う方法です。 以下のソースコードのように書きます。

int num = 4;

omp_set_num_threads(num);
#pragma omp parallel for
for (int i = 0; i < N; ++i) {
    hoge();
}

もう一つは#pragmanum_threadsを追加する方法です。

int num = 4;

#pragma omp parallel for num_threads(num)
for (int i = 0; i < N; ++i) {
    hoge();
}

ちなみにいずれの方法においても、指定するスレッド数はコンパイル時に決まっている必要はありません。 実行時に値を指定しても、その指定した値で並列化されます。

2つの方法の違い

omp_get_max_threads関数の戻り値が異なる

omp_set_num_threads関数を使用する方法では、omp_get_max_threads関数の戻り値が omp_set_num_threadsで指定した値になります。
一方num_threadsを追加する方法では、指定したスレッド数がomp_get_max_threads関数の戻り値に影響しません。

以下のソースコードで試します。

#include <iostream>
#include <omp.h>

int main(int argc, char* argv[]) {
    std::cout << "omp_get_max_threads = " << omp_get_max_threads() << std::endl;

#ifdef SWITCH
    omp_set_num_threads(4);
    #pragma omp parallel for
#else
    #pragma omp parallel for num_threads(4)
#endif
    for (int i = 0; i < 10; ++i) {
        #pragma omp critical
        {
            std::cout << "i = " << i
                << ", omp_get_thread_num" << omp_get_thread_num()
                << ", omp_get_num_threads = " << omp_get_num_threads()
                << ", omp_get_max_threads = " << omp_get_max_threads()
                << std::endl;
        }
    }

    std::cout << "omp_get_max_threads = " << omp_get_max_threads() << std::endl;

    return 0;
}

omp_set_num_threads関数を使用する方法(上記ソースコードのSWITCHが define されているとき)での 結果は以下のようになりました。 omp_set_num_threads関数の前後でomp_get_max_threads関数の戻り値が変化していることが分かります。 今回の場合8から4に変化しています。

omp_get_max_threads = 8
i = 0, omp_get_thread_num0, omp_get_num_threads = 4, omp_get_max_threads = 4
i = 1, omp_get_thread_num0, omp_get_num_threads = 4, omp_get_max_threads = 4
i = 2, omp_get_thread_num0, omp_get_num_threads = 4, omp_get_max_threads = 4
i = 8, omp_get_thread_num3, omp_get_num_threads = 4, omp_get_max_threads = 4
i = 9, omp_get_thread_num3, omp_get_num_threads = 4, omp_get_max_threads = 4
i = 6, omp_get_thread_num2, omp_get_num_threads = 4, omp_get_max_threads = 4
i = 7, omp_get_thread_num2, omp_get_num_threads = 4, omp_get_max_threads = 4
i = 3, omp_get_thread_num1, omp_get_num_threads = 4, omp_get_max_threads = 4
i = 4, omp_get_thread_num1, omp_get_num_threads = 4, omp_get_max_threads = 4
i = 5, omp_get_thread_num1, omp_get_num_threads = 4, omp_get_max_threads = 4
omp_get_max_threads = 4

次に、num_threadsを追加する方法(上記ソースコードのSWITCHが define されていないとき)での 結果は以下のようになりました。 omp_get_max_threads関数の戻り値が変化していないことが分かります。

omp_get_max_threads = 8
i = 0, omp_get_thread_num0, omp_get_num_threads = 4, omp_get_max_threads = 8
i = 1, omp_get_thread_num0, omp_get_num_threads = 4, omp_get_max_threads = 8
i = 2, omp_get_thread_num0, omp_get_num_threads = 4, omp_get_max_threads = 8
i = 8, omp_get_thread_num3, omp_get_num_threads = 4, omp_get_max_threads = 8
i = 9, omp_get_thread_num3, omp_get_num_threads = 4, omp_get_max_threads = 8
i = 6, omp_get_thread_num2, omp_get_num_threads = 4, omp_get_max_threads = 8
i = 7, omp_get_thread_num2, omp_get_num_threads = 4, omp_get_max_threads = 8
i = 3, omp_get_thread_num1, omp_get_num_threads = 4, omp_get_max_threads = 8
i = 4, omp_get_thread_num1, omp_get_num_threads = 4, omp_get_max_threads = 8
i = 5, omp_get_thread_num1, omp_get_num_threads = 4, omp_get_max_threads = 8
omp_get_max_threads = 8

そして、いずれの方法でも指定した並列数(今回の場合4)で動いていることが分かります。