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 libreria PPL offre le funzionalità seguenti:

  • Parallelismo attività: un meccanismo che funziona in cima a ThreadPool di Windows per eseguire diversi elementi di lavoro (attività) in parallelo

  • Algoritmi paralleli: algoritmi generici che funzionano sopra il runtime di concorrenza per agire sulle raccolte di dati in parallelo

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

Esempio

La libreria PPL fornisce un modello di programmazione simile alla libreria standard C++. 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 std::for_each della libreria standard C++ per attraversare la matrice e archivia i risultati in un oggetto std::vector. La versione parallela esegue la stessa attività, ma usa l'algoritmo PPL concurrency::p arallel_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 l'esempio usa un metodo ingenuo per calcolare i numeri di Fibonacci; Tuttavia, questo metodo illustra come il runtime di concorrenza può migliorare le prestazioni dei 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.

serial time: 9250 ms
parallel time: 5726 ms

fib(24): 46368
fib(26): 121393
fib(41): 165580141
fib(42): 267914296

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.

Posizione Descrizione
Parallelismo delle attività 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.