メモの日々


2023年01月19日(木) [長年日記]

[c++] OpenMPのサンプル

OpenMPの使い方を毎回忘却してしまうので例をメモしておく。

並列に偶数のvectorを構築する例。

#include <iostream>
#include <vector>

#include <omp.h>

std::vector<int> make_vector(int size)
{
  std::vector<int> result;

  #pragma omp parallel
  {
    std::vector<int> local;

    #pragma omp for nowait
    for (int i = 0; i < size; ++i) {
      if (i % 2 == 0) local.push_back(i);
    }

    #pragma omp critical
    result.insert(result.end(), local.begin(), local.end());
  }

  return result;
}

int main()
{
  const auto max_threads = omp_get_max_threads();
  std::cout << "max_threads = " << max_threads << std::endl;

  const auto v = make_vector(max_threads * 10);

  std::cout << "v.size = " << v.size() << std::endl;
  for (auto e : v) {
    std::cout << e << ", ";
  }
  std::cout << std::endl;
}
max_threads = 6
v.size = 30
0, 2, 4, 6, 8, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 30, 32, 34, 36, 38, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28,
  • コンパイル時にOpenMPの使用を明示する必要がある。GCCの場合は-fopenmpオプションを付与する。
  • #pragma omp parallelのあるブロックはマルチスレッドで実行される。ブロック内で宣言された変数はスレッドローカルな変数になる。ブロックの外で宣言された変数はスレッド間で共有する変数になる(のでアクセス時に排他制御が必要)。
  • #pragma omp forのあるforループはスレッド間で分担して実行される。デフォルトではループの末尾で全スレッドが待ち合わせるが、nowaitを指定すると待ち合せなくなる。
  • #pragma omp criticalのあるブロックはスレッド間で排他的に実行される。

上の例のmake_vector()は、#pragma omp declare reductionを使ってリダクション識別子を宣言すると明示的にcriticalを指定せずに書くことができる。ただ、コードが分かりにくくなるので積極的に使うべきかは微妙。

#pragma omp declare reduction ( \
  concat :  \
  std::vector<int> :  \
  omp_out.insert(omp_out.end(), omp_in.begin(), omp_in.end()) \
)

std::vector<int> make_vector(int size)
{
  std::vector<int> result;

  #pragma omp parallel for reduction(concat: result)
  for (int i = 0; i < size; ++i) {
    if (i % 2 == 0) result.push_back(i);
  }

  return result;
}
  • concatという、vectorを結合するリダクション識別子を宣言した。その内容を定義する際にはomp_inとomp_outという識別子を使うと決まっている。
  • 宣言したリダクション識別子は、ディレクティブのreduction節で使用する。reduction節ではリダクション識別子にスレッド間の共有変数(上の例ではresult)を適用する。
    • これにより、当該ブロック内での当該共有変数に対する操作はスレッドローカルに行われるようになる。
    • ブロックを抜ける際に、スレッドローカルな値が共有変数へ定義したリダクション内容で反映される。
    • (と理解した)
  • #pragma omp parallel forは最初の例で使った #pragma omp parallel と #pragma omp for をまとめた処理を行う。criticalを使わなくてよくなるのでこのディレクティブを使える。