Condividi tramite


PPL (Parallel Patterns Library)

La libreria PPL (Parallel Patterns Library) fornisce un modello di programmazione imperativa in grado di offrire scalabilità e semplicità per lo sviluppo di applicazioni simultanee. La libreria PPL si basa sulla pianificazione e sui componenti di gestione delle risorse del runtime di concorrenza. Innalza il livello di astrazione tra il codice dell'applicazione e il meccanismo di threading sottostante fornendo algoritmi generici indipendenti dai tipi e contenitori che agiscono sui dati in parallelo. La libreria PPL consente inoltre di sviluppare applicazioni adeguate fornendo alternative allo stato condiviso.

La Classe task e i tipi correlati che sono definiti in ppltasks.h sono portabili tra le varie piattaforme. Gli algoritmi paralleli e i contenitori non sono portabili.

La libreria PPL offre le funzionalità seguenti:

  • Parallelismo delle attività: un meccanismo per eseguire diversi elementi di lavoro (attività) in parallelo

  • Algoritmi paralleli: algoritmi generici che agiscono su raccolte di dati in parallelo

  • Contenitori e oggetti paralleli: tipi di contenitore generici che consentono l'accesso simultaneo sicuro ai relativi elementi

Esempio

La libreria PPL fornisce un modello di programmazione simile alla libreria STL (Standard Template Library). L'esempio seguente illustra molte funzionalità della libreria PPL. Vengono calcolati diversi numeri di Fibonacci in serie e in parallelo. Entrambi i calcoli agiscono su un oggetto std::array. L'esempio inoltre visualizza nella console il tempo necessario per eseguire entrambi i calcoli.

La versione seriale usa l'algoritmo STL std::for_each per attraversare la matrice e archivia i risultati in un oggetto std::vector. La versione parallela esegue la stessa attività usando però l'algoritmo PPL concurrency::parallel_for_each e archivia i risultati in un oggetto concurrency::concurrent_vector. La classe concurrent_vector consente a ogni iterazione del ciclo di aggiungere contemporaneamente gli elementi senza la necessità di sincronizzare l'accesso in scrittura al contenitore.

Poiché parallel_for_each agisce contemporaneamente, la versione parallela di questo esempio deve ordinare l'oggetto concurrent_vector per produrre gli stessi risultati della versione seriale.

Si noti che nell'esempio viene usato un metodo naïve per calcolare i numeri di Fibonacci; tuttavia, questo metodo illustra come il runtime di concorrenza possa migliorare le prestazioni di calcoli lunghi.

// parallel-fibonacci.cpp 
// compile with: /EHsc
#include <windows.h>
#include <ppl.h>
#include <concurrent_vector.h>
#include <array>
#include <vector>
#include <tuple>
#include <algorithm>
#include <iostream>

using namespace concurrency;
using namespace std;

// Calls the provided work function and returns the number of milliseconds  
// that it takes to call that function. 
template <class Function>
__int64 time_call(Function&& f)
{
   __int64 begin = GetTickCount();
   f();
   return GetTickCount() - begin;
}

// Computes the nth Fibonacci number. 
int fibonacci(int n)
{
   if(n < 2)
      return n;
   return fibonacci(n-1) + fibonacci(n-2);
}

int wmain()
{
   __int64 elapsed;

   // An array of Fibonacci numbers to compute. 
   array<int, 4> a = { 24, 26, 41, 42 };

   // The results of the serial computation.
   vector<tuple<int,int>> results1;

   // The results of the parallel computation.
   concurrent_vector<tuple<int,int>> results2;

   // Use the for_each algorithm to compute the results serially.
   elapsed = time_call([&] 
   {
      for_each (begin(a), end(a), [&](int n) {
         results1.push_back(make_tuple(n, fibonacci(n)));
      });
   });   
   wcout << L"serial time: " << elapsed << L" ms" << endl;

   // Use the parallel_for_each algorithm to perform the same task.
   elapsed = time_call([&] 
   {
      parallel_for_each (begin(a), end(a), [&](int n) {
         results2.push_back(make_tuple(n, fibonacci(n)));
      });

      // Because parallel_for_each acts concurrently, the results do not  
      // have a pre-determined order. Sort the concurrent_vector object 
      // so that the results match the serial version.
      sort(begin(results2), end(results2));
   });   
   wcout << L"parallel time: " << elapsed << L" ms" << endl << endl;

   // Print the results.
   for_each (begin(results2), end(results2), [](tuple<int,int>& pair) {
      wcout << L"fib(" << get<0>(pair) << L"): " << get<1>(pair) << endl;
   });
}

L'output di esempio seguente è relativo a un computer con quattro processori.

  

Ogni iterazione del ciclo richiede una quantità di tempo diversa per il completamento. Le prestazioni di parallel_for_each dipendono dall'operazione che richiede più tempo. Pertanto, non c'era da aspettarsi un miglioramento lineare delle prestazioni tra la versione seriale e quella parallela di questo esempio.

Argomenti correlati

Titolo

Descrizione

Parallelismo delle attività (runtime di concorrenza)

Descrive il ruolo delle attività e dei gruppi di attività nella libreria PPL.

Algoritmi paralleli

Descrive come usare gli algoritmi paralleli come parallel_for e parallel_for_each.

Contenitori e oggetti paralleli

Descrive i vari contenitori e oggetti paralleli forniti dalla libreria PPL.

Annullamento nella libreria PPL

Illustra come annullare il lavoro eseguito da un algoritmo parallelo.

Runtime di concorrenza

Descrive il runtime di concorrenza che semplifica la programmazione parallela e contiene i collegamenti ad argomenti correlati.