Cómo: Convertir un bucle OpenMP que usa la cancelación para usar el runtime de simultaneidad
Algunos bucles paralelos no necesitan que se ejecuten todas las iteraciones. Por ejemplo, un algoritmo que busca un valor puede finalizar una vez que se encuentra el valor. OpenMP no proporciona ningún mecanismo para interrumpir un bucle paralelo. Sin embargo, puede usar un valor booleano, o marca, para permitir que una iteración del bucle indique que se ha encontrado la solución. El runtime de simultaneidad proporciona funcionalidad que permite a una tarea cancelar otras tareas que todavía no se han iniciado.
En este ejemplo se muestra cómo convertir un bucle parallelfor de OpenMP que no necesita que se ejecuten todas las iteraciones para usar el mecanismo de cancelación del Runtime de simultaneidad.
Ejemplo
En este ejemplo se usa OpenMP y el Runtime de simultaneidad para implementar una versión paralela del algoritmo std::any_of. La versión de OpenMP de este ejemplo usa una marca para coordinar entre todas las iteraciones del bucle paralelo que se cumple la condición. La versión que emplea el Runtime de simultaneidad usa el método concurrency::structured_task_group::cancel para detener la operación global cuando se cumple la condición.
// 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 ejemplo produce el siguiente resultado:
Using OpenMP...
9114046 is in the array.
Using the Concurrency Runtime...
9114046 is in the array.
En la versión que usa OpenMP, todas las iteraciones del bucle se ejecutan, incluso cuando se establece la marca. Además, si una tarea fuera a tener tareas secundarias, la marca también tendría que estar disponible para que esas tareas secundarias comunicaran la cancelación. En el Runtime de simultaneidad, cuando se cancela un grupo de tareas, el runtime cancela todo el árbol de trabajo, incluidas las tareas secundarias. El algoritmo concurrency::parallel_for_each usa tareas para realizar el trabajo. Por tanto, cuando una iteración del bucle cancela la tarea raíz, también se cancela todo el árbol de cálculo. Cuando se cancela un árbol de trabajo, el runtime no inicia nuevas tareas. Sin embargo, el runtime permite que las tareas que se han iniciado finalicen. Por tanto, en el caso del algoritmo parallel_for_each
, las iteraciones del bucle activas pueden limpiar sus recursos.
En ambas versiones de este ejemplo, si la matriz contiene más de una copia del valor que se va a buscar, varias iteraciones del bucle pueden establecer simultáneamente el resultado y cancelar la operación en su conjunto. Puede usar una primitiva de sincronización, como una sección crítica, si el problema necesita que solo una tarea realice el trabajo cuando se cumple una condición.
También puede usar el control de excepciones para cancelar las tareas que usan la biblioteca PPL. Para obtener más información sobre la cancelación, vea Cancelación en la biblioteca PPL.
Para más información sobre parallel_for_each
y otros algoritmos en paralelo, consulta Algoritmos paralelos.
Compilar el código
Copie el código de ejemplo y péguelo en un proyecto de Visual Studio o en un archivo denominado concrt-omp-parallel-any-of.cpp
y, después, ejecute el siguiente comando en una ventana del símbolo del sistema de Visual Studio.
cl.exe /EHsc /openmp concrt-omp-parallel-any-of.cpp
Consulte también
Migración de OpenMP al Runtime de simultaneidad
Cancelación en la biblioteca PPL
Algoritmos paralelos