Compartilhar via


Como converter um loop OpenMP que usa cancelamento para usar o runtime de simultaneidade

Alguns loops paralelos não exigem que todas as iterações sejam executadas. Por exemplo, um algoritmo que pesquisa um valor pode ser encerrado depois que o valor for encontrado. O OpenMP não fornece um mecanismo para sair de um loop paralelo. No entanto, você pode usar um valor booliano, ou sinalizador, para habilitar uma iteração do loop e indicar que a solução foi encontrada. O Runtime de Simultaneidade oferece uma funcionalidade que permite que uma tarefa cancele outras tarefas que ainda não foram iniciadas.

Este exemplo demonstra como converter um loop parallelfor do OpenMP que não exige que todas as iterações sejam executadas para usar o mecanismo de cancelamento do Runtime de Simultaneidade.

Exemplo

Este exemplo usa o OpenMP e o Runtime de Simultaneidade para implementar uma versão paralela do algoritmo std::any_of. A versão OpenMp deste exemplo usa um sinalizador para coordenar entre todas as iterações de loop paralelo que a condição foi atendida. A versão que usa o Runtime de Simultaneidade emprega o método concurrency::structured_task_group::cancel para interromper a operação geral quando a condição é atendida.

// 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;
   }
}

Este exemplo gerencia a seguinte saída.

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

Na versão que usa OpenMP, todas as iterações do loop são executadas, mesmo quando o sinalizador é definido. Além disso, se uma tarefa tivesse alguma tarefa filho, o sinalizador também teria que estar disponível para essas tarefas filho para comunicar o cancelamento. No Runtime de Simultaneidade, quando um grupo de tarefas é cancelado, o runtime cancela toda a árvore de trabalho, incluindo tarefas filho. O algoritmo concurrency::p arallel_for_each usa tarefas para executar o trabalho. Portanto, quando uma iteração do loop cancela a tarefa raiz, toda a árvore de computação também é cancelada. Quando uma árvore de trabalho é cancelada, o runtime não inicia novas tarefas. No entanto, o runtime permite que tarefas que já começaram sejam concluídas. Portanto, no caso do algoritmo parallel_for_each, iterações de loop ativo podem limpar os recursos.

Em ambas as versões deste exemplo, se a matriz contiver mais de uma cópia do valor a ser pesquisado, várias iterações de loop poderão definir simultaneamente o resultado e cancelar a operação geral. Você pode usar um primitivo de sincronização, como uma seção crítica, se o problema exigir que apenas uma tarefa execute o trabalho quando uma condição for atendida.

Você também pode usar o tratamento de exceções para cancelar tarefas que usam o PPL. Para saber mais sobre cancelamento, confira Cancelamento no PPL.

Para obter mais informações sobre parallel_for_each e outros algoritmos paralelos, consulte Algoritmos paralelos.

Compilando o código

Copie o código de exemplo e cole-o em um projeto do Visual Studio, ou cole-o em um arquivo chamado concrt-omp-parallel-any-of.cpp e execute o comando a seguir em uma janela do Prompt de comando do Visual Studio.

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

Confira também

Migrando do OpenMP para o runtime de simultaneidade
Cancelamento no PPL
Algoritmos paralelos