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 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). Nell'esempio seguente vengono illustrate 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 utilizza 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à utilizzando 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 utilizzato 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 (a.begin(), a.end(), [&](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 (a.begin(), a.end(), [&](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(results2.begin(), results2.end());
   });   
   wcout << L"parallel time: " << elapsed << L" ms" << endl << endl;

   // Print the results.
   for_each (results2.begin(), results2.end(), [](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'ultima operazione che viene completata. Pertanto, non è possibile aspettarsi miglioramenti delle prestazioni lineari tra le versioni seriale e parallela di questo esempio.

Argomenti correlati

Titolo

Descrizione

Parallelismo delle attività (runtime di concorrenza)

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

Algoritmi paralleli

Viene descritto come utilizzare gli algoritmi paralleli come parallel_for e parallel_for_each.

Contenitori e oggetti paralleli

Vengono descritti i vari contenitori e oggetti paralleli forniti dalla libreria PPL.

Annullamento nella libreria PPL

Viene illustrato come annullare il lavoro eseguito da un algoritmo parallelo.

Runtime di concorrenza

Viene descritto il runtime di concorrenza che semplifica la programmazione parallela e vengono forniti i collegamenti ad argomenti correlati.