Algoritmi paralleli

La libreria PPL (Parallel Patterns Library) fornisce gli algoritmi per svolgere simultaneamente il lavoro sulle raccolte di dati. Questi algoritmi sono simili a quelli forniti dalla libreria STL (Standard Template Library).

Gli algoritmi paralleli sono costituiti da funzionalità esistenti nel runtime di concorrenza. L'algoritmo Concurrency::parallel_for utilizza, ad esempio, un oggetto Concurrency::structured_task_group per eseguire le iterazioni del ciclo parallelo. L'algoritmo parallel_for partiziona il lavoro in modo ottimale in base al numero di risorse di elaborazione disponibili.

Sezioni

In questo argomento vengono descritti in dettaglio gli algoritmi paralleli seguenti:

  • Algoritmo parallel_for

  • Algoritmo parallel_for_each

  • Algoritmo parallel_invoke

Algoritmo parallel_for

L'algoritmo Concurrency::parallel_for esegue ripetutamente la stessa attività in parallelo. Ognuna di queste attività contiene i parametri per un valore dell'iterazione. Questo algoritmo è utile quando è presente un corpo del ciclo che non condivide le risorse tra le iterazioni del ciclo.

L'algoritmo parallel_for partiziona le attività in modo ottimale per l'esecuzione in parallelo. Utilizza un algoritmo di acquisizione del lavoro per bilanciare le partizioni quando i carichi di lavoro non sono bilanciati. Quando un'iterazione del ciclo si blocca in modo cooperativo, il runtime ridistribuisce l'intervallo delle iterazioni assegnato al thread corrente agli altri thread o processori. In modo analogo, quando un thread completa un intervallo di iterazioni, il runtime ridistribuisce il lavoro degli altri thread a tale thread. L'algoritmo parallel_for supporta anche il parallelismo annidato. Quando un ciclo parallelo contiene un altro ciclo parallelo, il runtime coordina le risorse di elaborazione tra i corpi del ciclo in modo efficiente per l'esecuzione parallela.

L'algoritmo parallel_for dispone di due versioni di overload. La prima versione accetta un valore iniziale, un valore finale e una funzione lavoro, ovvero un'espressione lambda, un oggetto funzione o un puntatore a funzione. La seconda versione accetta un valore iniziale, un valore finale, un valore di incremento e una funzione lavoro. La prima versione di questa funzione utilizza 1 come valore di incremento.

È possibile convertire molti cicli for affinché venga utilizzato parallel_for. Tuttavia, tra l'algoritmo parallel_for e l'istruzione for esistono le differenze seguenti:

  • L'algoritmo parallel_for non esegue le attività in un ordine predeterminato.

  • L'algoritmo parallel_for non supporta condizioni di terminazione arbitraria. L'algoritmo parallel_for si arresta quando il valore corrente della variabile di iterazione è inferiore di un'unità rispetto a _Last.

  • Il parametro di tipo _Index_type deve essere un tipo integrale. Questo tipo integrale può essere con segno o senza segno.

  • L'iterazione del ciclo deve essere in avanti. L'algoritmo parallel_for genera un'eccezione di tipo std::invalid_argument se il parametro _Step è minore di 1.

  • Il meccanismo di gestione delle eccezioni per l'algoritmo parallel_for differisce da quello di un ciclo for. Se si verificano più eccezioni contemporaneamente nel corpo di un ciclo parallelo, il runtime propaga solo una delle eccezioni nel thread che ha chiamato parallel_for. Inoltre, quando un'iterazione del ciclo genera un'eccezione, il runtime non interrompe immediatamente il ciclo globale. Il ciclo passa invece allo stato annullato e il runtime rimuove tutte le attività che non sono ancora state avviate. Per ulteriori informazioni sulla gestione delle eccezioni e sugli algoritmi paralleli, vedere Gestione delle eccezioni nel runtime di concorrenza.

Sebbene l'algoritmo parallel_for non supporti condizioni di terminazione arbitraria, è possibile utilizzare l'annullamento per arrestare tutte le attività. Per ulteriori informazioni sull'annullamento, vedere Annullamento nella libreria PPL.

Nota

È possibile che il costo di pianificazione che risulta dal bilanciamento del carico e il supporto di funzionalità come l'annullamento non superino i vantaggi dell'esecuzione del corpo del ciclo in parallelo, soprattutto quando il corpo del ciclo è relativamente piccolo.

Esempio

Nell'esempio seguente viene mostrata la struttura di base dell'algoritmo parallel_for. L'esempio visualizza nella console ogni valore nell'intervallo [1, 5] in parallelo.

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

Questo esempio produce l'output seguente:

1 2 4 3 5

Poiché l'algoritmo parallel_for agisce su ogni elemento in parallelo, l'ordine in cui vengono visualizzati i valori nella console sarà diverso.

Per un esempio completo in cui viene utilizzato l'algoritmo parallel_for, vedere Procedura: scrivere un ciclo parallel_for.

[vai all'inizio]

Algoritmo parallel_for_each

L'algoritmo Concurrency::parallel_for_each esegue le attività su un contenitore iterativo, ad esempio quelli forniti dalla libreria STL, in parallelo. Utilizza la stessa logica di partizionamento utilizzata dall'algoritmo parallel_for.

L'algoritmo parallel_for_each è simile all'algoritmo std::for_each STL, con l'unica differenza che l'algoritmo parallel_for_each esegue le attività simultaneamente. Analogamente agli altri algoritmi paralleli, parallel_for_each non esegue le attività in un ordine specifico.

Sebbene l'algoritmo parallel_for_each possa essere utilizzato sia con gli iteratori in avanti che con gli iteratori di accesso casuale, le prestazioni sono migliori con gli iteratori di accesso casuale.

Esempio

Nell'esempio seguente viene mostrata la struttura di base dell'algoritmo parallel_for_each. L'esempio visualizza nella console ogni valore in un oggetto std::array in parallelo.

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

Questo esempio produce l'output seguente:

4 5 1 2 3

Poiché l'algoritmo parallel_for_each agisce su ogni elemento in parallelo, l'ordine in cui vengono visualizzati i valori nella console sarà diverso.

Per un esempio completo in cui viene utilizzato l'algoritmo parallel_for_each, vedere Procedura: scrivere un ciclo parallel_for_each.

[vai all'inizio]

Algoritmo parallel_invoke

L'algoritmo Concurrency::parallel_invoke esegue un set di attività in parallelo. Non restituisce alcun valore finché non vengono completate tutte le attività. Questo algoritmo è utile quando si desidera eseguire contemporaneamente diverse attività indipendenti.

L'algoritmo parallel_invoke accetta come parametri una serie di funzioni lavoro, ovvero funzioni lambda, oggetti funzione o puntatori a funzione. Viene eseguito l'overload dell'algoritmo parallel_invoke per accettare da due a dieci parametri. Ogni funzione che si passa a parallel_invoke deve accettare zero parametri.

Analogamente agli altri algoritmi paralleli, parallel_invoke non esegue le attività in un ordine specifico. Nell'argomento Parallelismo delle attività (runtime di concorrenza) viene illustrato come l'algoritmo parallel_invoke viene correlato alle attività e ai gruppi di attività.

Esempio

Nell'esempio seguente viene mostrata la struttura di base dell'algoritmo parallel_invoke. In questo esempio viene chiamata la funzione twice contemporaneamente in tre variabili locali e viene visualizzato il risultato nella console.

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

Questo esempio produce l'output seguente:

108 11.2 HelloHello

Per gli esempi completi in cui viene utilizzato l'algoritmo parallel_invoke, vedere Procedura: utilizzare parallel_invoke per scrivere una routine di ordinamento in parallelo e Procedura: utilizzare parallel_invoke per eseguire operazioni in parallelo.

[vai all'inizio]

Argomenti correlati

Riferimento

Funzione parallel_for

Funzione parallel_for_each

Funzione parallel_invoke