Algoritmos paralelos

La Biblioteca de modelos de procesamiento paralelo (PPL) proporciona algoritmos que se aplican a colecciones de datos de manera simultánea. Estos algoritmos se parecen a los que proporciona la Biblioteca de plantillas estándar (STL).

Los algoritmos paralelos se crean a partir de funcionalidad existente en el Runtime de simultaneidad. Por ejemplo, el algoritmo Concurrency::parallel_for usa un objeto Concurrency::structured_task_group para realizar iteraciones de bucle en paralelo. El algoritmo parallel_for particiona el trabajo de una manera óptima en función del número de recursos de computación disponibles.

Secciones

En este tema se describen detalladamente los siguientes algoritmos paralelos:

  • Algoritmo parallel_for

  • Algoritmo parallel_for_each

  • Algoritmo parallel_invoke

Algoritmo parallel_for

El algoritmo Concurrency::parallel_for realiza repetidamente la misma tarea en paralelo. Cada una de estas tareas se parametriza mediante un valor de iteración. Este algoritmo es útil si hay un cuerpo de bucle que no comparte los recursos entre las iteraciones de ese bucle.

El algoritmo parallel_for particiona las tareas de una manera óptima para la ejecución en paralelo. Usa un algoritmo de robo de trabajo para equilibrar estas particiones cuando las cargas de trabajo están desequilibradas. Cuando una iteración del bucle bloquea de forma cooperativa, el runtime redistribuye a otros subprocesos o procesadores el intervalo de iteraciones asignado al subproceso actual. Del mismo modo, cuando un subproceso completa un intervalo de iteraciones, el runtime redistribuye el trabajo de otros subprocesos a ese subproceso. El algoritmo parallel_for también admite paralelismo anidado. Cuando un bucle paralelo contiene otro bucle paralelo, el runtime coordina los recursos de procesamiento entre los cuerpos de bucle de manera eficaz para la ejecución en paralelo.

El algoritmo parallel_for tiene dos versiones sobrecargadas. La primera versión toma un valor inicial, un valor final y una función de trabajo (una expresión lambda, objeto de función o puntero a función). La segunda versión toma un valor inicial, un valor final, un valor de incremento y una función de trabajo. La primera versión de esta función usa 1 como valor de incremento.

Puede convertir muchos bucles for para usar parallel_for. Sin embargo, el algoritmo parallel_for se diferencia de la instrucción for en los aspectos siguientes:

  • El algoritmo parallel_for de parallel_for no ejecuta las tareas en un orden predeterminado.

  • El algoritmo parallel_for no admite las condiciones de finalización arbitrarias. El algoritmo parallel_for se detiene cuando el valor actual de la variable de iteración es uno menos que _Last.

  • El parámetro de tipo _Index_type debe ser de tipo entero. Este tipo entero puede ser con signo o sin signo.

  • La iteración del bucle debe ser hacia delante. El algoritmo parallel_for produce una excepción de tipo std::invalid_argument si el parámetro _Step es menor que 1.

  • El mecanismo de control de excepciones para el algoritmo parallel_for difiere del de un bucle for. Si se producen varias excepciones simultáneamente en el cuerpo de un bucle paralelo, el runtime propaga solo una de las excepciones al subproceso que llamó a parallel_for. Además, cuando una iteración del bucle produce una excepción, el runtime no detiene inmediatamente el bucle completo. En su lugar, el bucle se coloca en el estado cancelado y el runtime descarta cualquier tarea que no se haya iniciado. Para obtener más información sobre el control de excepciones y los algoritmos paralelos, vea Control de excepciones en el runtime de simultaneidad.

Aunque el algoritmo parallel_for no admite condiciones de finalización arbitrarias, se puede usar la cancelación para detener todas las tareas. Para obtener más información sobre la cancelación, vea Cancelación en la biblioteca PPL.

Nota

El costo de programación resultante del equilibrio de carga y la compatibilidad con características como la cancelación podría no superar las ventajas que supone ejecutar el cuerpo del bucle en paralelo, sobre todo cuando el cuerpo del bucle es relativamente pequeño.

Ejemplo

En el ejemplo siguiente se muestra la estructura básica del algoritmo parallel_for. En este ejemplo se imprime en la consola cada valor del intervalo [1, 5] en paralelo.

// parallel-for-structure.cpp
// compile with: /EHsc
#include <ppl.h>
#include <array>
#include <sstream>
#include <iostream>

using namespace Concurrency;
using namespace std;

int wmain()
{
   // Print each value from 1 to 5 in parallel.
   parallel_for(1, 6, [](int value) {
      wstringstream ss;
      ss << value << L' ';
      wcout << ss.str();
   });
}

Este ejemplo genera la siguiente salida de ejemplo:

1 2 4 3 5

Puesto que el algoritmo parallel_for actúa sobre cada elemento en paralelo, el orden en el que los valores se imprimen en la consola varía.

Para obtener un ejemplo completo que usa el algoritmo parallel_for, vea Cómo: Escribir un bucle parallel_for.

[Ir al principio]

Algoritmo parallel_for_each

El algoritmo Concurrency::parallel_for_each realiza las tareas en un contenedor iterativo, como los proporcionados por STL, en paralelo. Usa la misma lógica de creación de particiones que el algoritmo parallel_for.

El algoritmo parallel_for_each es similar al algoritmo std::for_each de STL, excepto que parallel_for_each ejecuta las tareas simultáneamente. Como sucede con otros algoritmos paralelos, parallel_for_each no ejecuta las tareas en un orden concreto.

Aunque el algoritmo parallel_for_each funciona con iteradores hacia delante e iteradores del acceso aleatorio, funciona mejor con estos últimos.

Ejemplo

En el ejemplo siguiente se muestra la estructura básica del algoritmo parallel_for_each. En este ejemplo se imprime en la consola cada valor de un objeto std::array en paralelo.

// parallel-for-each-structure.cpp
// compile with: /EHsc
#include <ppl.h>
#include <array>
#include <sstream>
#include <iostream>

using namespace Concurrency;
using namespace std;

int wmain()
{
   // Create an array of integer values.
   array<int, 5> values = { 1, 2, 3, 4, 5 };

   // Print each value in the array in parallel.
   parallel_for_each(values.begin(), values.end(), [](int value) {
      wstringstream ss;
      ss << value << L' ';
      wcout << ss.str();
   });
}

Este ejemplo genera la siguiente salida de ejemplo:

4 5 1 2 3

Puesto que el algoritmo parallel_for_each actúa sobre cada elemento en paralelo, el orden en el que los valores se imprimen en la consola varía.

Para obtener un ejemplo completo que usa el algoritmo parallel_for_each, vea Cómo: Escribir un bucle parallel_for_each.

[Ir al principio]

Algoritmo parallel_invoke

El algoritmo Concurrency::parallel_invoke ejecuta un conjunto de tareas en paralelo. No vuelve hasta que finaliza cada tarea. Este algoritmo es útil cuando hay varias tareas independientes que se desea ejecutar al mismo tiempo.

El algoritmo parallel_invoke toma como parámetros una serie de funciones de trabajo (funciones lambda, objetos de función o punteros a función). El algoritmo parallel_invoke se sobrecarga para tomar entre dos y diez parámetros. Cada función que se pase a parallel_invoke no debe tomar ningún parámetro.

Como sucede con otros algoritmos paralelos, parallel_invoke no ejecuta las tareas en un orden concreto. En el tema Paralelismo de tareas (Runtime de simultaneidad) se explica cómo se relaciona el algoritmo parallel_invoke con tareas y grupos de tareas.

Ejemplo

En el ejemplo siguiente se muestra la estructura básica del algoritmo parallel_invoke. En este ejemplo se llama simultáneamente a la función twice en tres variables locales y se imprime el resultado en la consola.

// parallel-invoke-structure.cpp
// compile with: /EHsc
#include <ppl.h>
#include <string>
#include <iostream>

using namespace Concurrency;
using namespace std;

// Returns the result of adding a value to itself.
template <typename T>
T twice(const T& t) {
   return t + t;
}

int wmain()
{
   // Define several values.
   int n = 54;
   double d = 5.6;
   wstring s = L"Hello";

   // Call the twice function on each value concurrently.
   parallel_invoke(
      [&n] { n = twice(n); },
      [&d] { d = twice(d); },
      [&s] { s = twice(s); }
   );

   // Print the values to the console.
   wcout << n << L' ' << d << L' ' << s << endl;
}

Este ejemplo produce el resultado siguiente:

108 11.2 HelloHello

Para obtener ejemplos completos que usan el algoritmo parallel_invoke, vea Cómo: Usar parallel.invoke para escribir una rutina de ordenación en paralelo y Cómo: Usar parallel.invoke para ejecutar operaciones paralelas.

[Ir al principio]

Temas relacionados

Referencia

parallel_for (Función)

parallel_for_each (Función)

parallel_invoke (Función)