方法: キャンセル処理を使用する OpenMP ループを変換し、コンカレンシー ランタイムを使用する

並列ループによっては、すべての反復を必ずしも実行する必要はありません。 たとえば、値を検索するアルゴリズムは、値が見つかれば終了できます。 OpenMP には、並列ループを抜けるための機構は用意されていません。 ただし、ブール値 (フラグ) を使用して、ループの反復処理時に、解決策が見つかったことを通知できます。 コンカレンシー ランタイムの場合、先行のタスクによって、まだ開始されていない他のタスクを取り消すことができます。

この例では、必ずしもすべての反復を実行する必要のない OpenMP parallel for ループを変換して、同時実行ランタイムの取り消し機構を使用する方法を示します。

この例では、OpenMP と同時実行ランタイムの両方を使用して、std::any_of アルゴリズムの並列バージョンを実装しています。 この例に示す OpenMP バージョンでは、フラグを使用して、条件が満たされたときにすべての並列ループ反復で調整が行われるようにしています。 同時実行ランタイムを使用するバージョンでは、concurrency::structured_task_group::cancel メソッドを使用して、条件が満たされたときに操作全体を停止します。

// concrt-omp-parallel-any-of.cpp
// compile with: /EHsc /openmp
#include <ppl.h>
#include <array>
#include <random>
#include <iostream>

using namespace concurrency;
using namespace std;

// Uses OpenMP to determine whether a condition exists in 
// the specified range of elements.
template <class InIt, class Predicate>
bool omp_parallel_any_of(InIt first, InIt last, const Predicate& pr)
{
   typedef typename std::iterator_traits<InIt>::value_type item_type;

   // A flag that indicates that the condition exists.
   bool found = false;

   #pragma omp parallel for
      for (int i = 0; i < static_cast<int>(last-first); ++i)
      {
         if (!found)
         {
            item_type& cur = *(first + i);

            // If the element satisfies the condition, set the flag to 
            // cancel the operation.
            if (pr(cur)) {
               found = true;
            }
         }
      }

   return found;
}

// Uses the Concurrency Runtime to determine whether a condition exists in 
// the specified range of elements.
template <class InIt, class Predicate>
bool concrt_parallel_any_of(InIt first, InIt last, const Predicate& pr)
{
   typedef typename std::iterator_traits<InIt>::value_type item_type;

   structured_task_group tasks;

   // Create a predicate function that cancels the task group when
   // an element satisfies the condition.
   auto for_each_predicate = [&pr, &tasks](const item_type& cur) {
      if (pr(cur)) {
         tasks.cancel();
      }
   };

   // Create a task that calls the predicate function in parallel on each
   // element in the range.
   auto task = make_task([&]() {
       parallel_for_each(first, last, for_each_predicate);
   });

   // The condition is satisfied if the task group is in the cancelled state.
   return tasks.run_and_wait(task) == canceled;
}

int wmain()
{
   // The length of the array.
   const size_t size = 100000;
   
   // Create an array and initialize it with random values.
   array<int, size> a;   
   generate(begin(a), end(a), mt19937(42));

   // Search for a value in the array by using OpenMP and the Concurrency Runtime.

   const int what = 9114046;
   auto predicate = [what](int n) -> bool { 
      return (n == what);
   };

   wcout << L"Using OpenMP..." << endl;
   if (omp_parallel_any_of(begin(a), end(a), predicate))
   {
      wcout << what << L" is in the array." << endl;
   }
   else
   {
      wcout << what << L" is not in the array." << endl;
   }

   wcout << L"Using the Concurrency Runtime..." << endl;
   if (concrt_parallel_any_of(begin(a), end(a), predicate))
   {
      wcout << what << L" is in the array." << endl;
   }
   else
   {
      wcout << what << L" is not in the array." << endl;
   }
}

この例を実行すると、次の出力が生成されます。

Using OpenMP...
9114046 is in the array.
Using the Concurrency Runtime...
9114046 is in the array.

OpenMP を使用するバージョンでは、フラグが設定されている場合でも、すべてのループ反復が実行されます。 さらに、タスクに子タスクが含まれている場合は、それらの子タスクにもフラグによって取り消しが通知される必要があります。 同時実行ランタイムでは、タスク グループが取り消されると、ランタイムは子タスクを含む処理ツリー全体を取り消します。 concurrency::parallel_for_each アルゴリズムでは、複数のタスクを使用して処理を実行します。 そのため、ループの 1 つの反復でルート タスクが取り消されると、計算ツリー全体が取り消されます。 処理ツリーが取り消された場合、ランタイムは新しいタスクを開始しません。 ただし、既に開始されているタスクは終了できます。 したがって、parallel_for_each アルゴリズムの場合、アクティブなループ反復によってリソースがクリーンアップされることがあります。

この例に示すどちらのバージョンでも、配列に検索対象値の複数のコピーが含まれている場合は、複数のループ反復でそれぞれ同時に結果が設定され、操作全体が取り消されることがあります。 条件が満たされたときに 1 つのタスクだけが実行されるようにする必要がある場合は、クリティカル セクションなどの同期プリミティブを使用できます。

また、例外処理を使用して、PPL を使用するタスクを取り消すこともできます。 取り消し処理の詳細については、PPL における取り消し処理 をご覧ください。

parallel_for_each および他の並列アルゴリズムの詳細については、「並列アルゴリズム」を参照してください。

コードのコンパイル

コード例をコピーし、Visual Studio プロジェクトに貼り付けるか、concrt-omp-parallel-any-of.cpp という名前のファイルに貼り付けてから、Visual Studio のコマンド プロンプト ウィンドウで次のコマンドを実行します。

cl.exe /EHsc /openmp concrt-omp-parallel-any-of.cpp

関連項目

OpenMP からコンカレンシー ランタイムへの移行
PPL における取り消し処理
並列アルゴリズム