Bibliothèque de modèles parallèles

La Bibliothèque de modèles parallèles (PPL) fournit un modèle de programmation essentiel qui favorise l’évolutivité et la facilité d’utilisation dans le cadre du développement d’applications simultanées. La bibliothèque PPL repose sur les composants de planification et de gestion des ressources du runtime d'accès concurrentiel. Elle élève le niveau d'abstraction entre le code de votre application et le mécanisme de threading sous-jacent en fournissant des algorithmes génériques de type sécurisé et des conteneurs qui agissent sur les données en parallèle. La bibliothèque PPL vous permet également de développer des applications qui évoluent en fournissant des alternatives à l'état partagé.

La bibliothèque PPL offre les fonctionnalités suivantes :

  • Parallélisme des tâches : mécanisme qui fonctionne au-dessus de Windows ThreadPool pour exécuter plusieurs éléments de travail (tâches) en parallèle

  • Algorithmes parallèles : algorithmes génériques qui fonctionnent en plus du runtime d’accès concurrentiel pour agir sur des collections de données en parallèle

  • Conteneurs et objets parallèles : types de conteneurs génériques qui fournissent un accès simultané sécurisé à leurs éléments

Exemple

Le PPL fournit un modèle de programmation qui ressemble à la bibliothèque C++ Standard. L'exemple suivant illustre de nombreuses fonctionnalités de la bibliothèque PPL. Il calcule plusieurs nombres de Fibonacci en série et en parallèle. Les deux calculs agissent sur un objet std ::array . L'exemple affiche également sur la console le temps requis pour effectuer les deux calculs.

La version série utilise l’algorithme std ::for_each de la bibliothèque standard C++ pour parcourir le tableau et stocker les résultats dans un objet std ::vector. La version parallèle effectue la même tâche, mais utilise la concurrence PPL  ::p arallel_for_each et stocke les résultats dans un objet concurrency ::concurrent_vector . La classe concurrent_vector permet à chaque itération de boucle d'ajouter simultanément des éléments sans avoir à synchroniser l'accès en écriture au conteneur.

Comme parallel_for_each agit simultanément, la version parallèle de cet exemple doit trier l'objet concurrent_vector pour produire les mêmes résultats que la version en série.

Notez que l'exemple utilise une méthode naïve pour calculer les nombres de Fibonacci ; toutefois, cette méthode illustre comment le runtime d'accès concurrentiel peut améliorer les performances de longs calculs.

// 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'exemple de sortie suivant concerne un ordinateur équipé de quatre processeurs.

serial time: 9250 ms
parallel time: 5726 ms

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

Chaque itération de la boucle nécessite une durée de temps différente pour s'exécuter. Les performances de parallel_for_each sont limitées par l'opération qui se termine en dernier. Par conséquent, vous ne devriez pas escompter des améliorations de performances linéaires entre les versions série et parallèle de l'exemple.

Intitulé Description
Parallélisme des tâches Décrit le rôle des tâches et des groupes de tâches dans la bibliothèque PPL.
Algorithmes parallèles Décrit comment utiliser des algorithmes parallèles tels que parallel_for et parallel_for_each.
Conteneurs et objets parallèles Décrit les différents objets et conteneurs parallèles fournis par la bibliothèque PPL.
Annulation dans la bibliothèque de modèles parallèles Explique comment annuler le travail effectué par un algorithme parallèle.
Concurrency Runtime Décrit le runtime d'accès concurrentiel, qui simplifie la programmation parallèle, et contient des liens vers les rubriques connexes.