Partager via


Algorithmes parallèles

La Bibliothèque de modèles parallèles (PPL) fournit des algorithmes qui exécutent simultanément du travail sur des collections de données. Ces algorithmes ressemblent à ceux fournis par la Bibliothèque de modèles Standard (STL, Standard Template Library).

Les algorithmes parallèles sont composés de fonctionnalités existantes du runtime d'accès concurrentiel. Par exemple, l'algorithme Concurrency::parallel_for utilise un objet Concurrency::structured_task_group pour effectuer les itérations de boucle parallèles. L'algorithme parallel_for partitionne le travail de façon optimale en fonction de la quantité de ressources de calcul disponible.

Sections

Cette rubrique décrit les algorithmes parallèles suivants en détail :

  • Algorithme parallel_for

  • Algorithme parallel_for_each

  • Algorithme parallel_invoke

Algorithme parallel_for

L'algorithme Concurrency::parallel_for effectue à plusieurs reprises la même tâche en parallèle. Chacune de ces tâches est peut être paramétrée par une valeur d'itération. Cet algorithme est utile lorsque vous avez un corps de boucle qui ne partage pas de ressources parmi les itérations de cette boucle.

L'algorithme parallel_for partitionne les tâches de façon optimale pour une exécution en parallèle. Il utilise un algorithme de vol de travail pour équilibrer ces partitions lorsque les charges de travail sont déséquilibrées. Lorsqu'une itération de boucle effectue un blocage coopératif, le runtime redistribue la plage des itérations assignée au thread actuel à d'autres threads ou processeurs. De même, lorsqu'un thread termine une plage d'itérations, le runtime redistribue le travail d'autres threads sur ce thread. L'algorithme parallel_for prend également en charge le parallélisme imbriqué. Lorsqu'une boucle parallèle contient une autre boucle parallèle, le runtime coordonne le traitement efficace des ressources entre les corps des boucles pour une exécution en parallèle.

L'algorithme parallel_for a deux versions surchargées. La première version prend une valeur de départ, une valeur de fin et une fonction de travail (expression lambda, objet de fonction ou pointeur fonction). La deuxième version prend une valeur de départ, une valeur de fin, une valeur d'étape et une fonction de travail. La première version de cette fonction utilise 1 comme valeur d'étape.

Vous pouvez convertir de nombreuses boucles for de façon à utiliser parallel_for. Cependant, l'algorithme parallel_for diffère de l'instruction for des manières suivantes :

  • L'algorithme parallel_for parallel_for n'exécute pas les tâches dans un ordre prédéterminé.

  • L'algorithme parallel_for ne prend pas en charge les conditions d'arrêt arbitraires. L'algorithme parallel_for s'arrête lorsque la valeur actuelle de la variable d'itération est inférieure de 1 à _Last.

  • Le paramètre de type _Index_type doit être un type entier. Ce type intégral peut être signé ou non signé.

  • L'itération de boucle doit être vers l'avant. L'algorithme parallel_for lève une exception de type std::invalid_argument si le paramètre _Step est inférieur à 1.

  • Le mécanisme de gestion des exceptions pour l'algorithme parallel_for diffère de celui utilisé pour une boucle for. Si plusieurs exceptions se produisent simultanément dans un corps de boucle parallèle, le runtime propage une seule des exceptions sur le thread appelé parallel_for. De plus, lorsqu'une itération de boucle lève une exception, le runtime n'arrête pas immédiatement la boucle globale. Au lieu de cela, l'état de la boucle devient « annulé » et le runtime ignore toutes les tâches qui n'ont pas encore commencé. Pour plus d'informations sur la gestion des exceptions et les algorithmes parallèles, consultez Gestion des exceptions dans le runtime d'accès concurrentiel.

Bien que l'algorithme parallel_for ne prenne pas en charge les conditions d'arrêt arbitraires, vous pouvez utiliser l'annulation pour arrêter toutes les tâches. Pour plus d'informations sur l'annulation, consultez Annulation dans la bibliothèque de modèles parallèles.

Notes

Le coût de planification qui résulte de l'équilibrage de la charge et de la prise en charge des fonctionnalités telles que l'annulation ne l'emporte pas sur les avantages qui découlent de l'exécution du corps de la boucle en parallèle, surtout lorsque le corps de la boucle est relativement petit.

Exemple

L'exemple suivant illustre la structure de base de l'algorithme parallel_for. Cet exemple imprime sur la console chaque valeur de la plage [1, 5] en parallèle.

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

Cet exemple génère l'exemple de sortie suivant :

1 2 4 3 5

Étant donné que l'algorithme parallel_for agit sur chaque élément en parallèle, l'ordre dans lequel les valeurs sont imprimées sur la console varie.

Pour obtenir un exemple complet utilisant l'algorithme parallel_for, consultez Comment : écrire une boucle parallel_for.

[retour en haut]

Algorithme parallel_for_each

L'algorithme Concurrency::parallel_for_each effectue les tâches sur un conteneur itératif, comme ceux fournis par la bibliothèque STL, en parallèle. Il utilise la même logique de partitionnement que l'algorithme parallel_for.

L'algorithme parallel_for_each ressemble à l'algorithme std::for_each STL, à l'exception près que l'algorithme parallel_for_each exécute les tâches simultanément. Comme d'autres algorithmes parallèles, parallel_for_each n'exécute pas les tâches dans un ordre spécifique.

Bien que l'algorithme parallel_for_each fonctionne sur les itérateurs avancés et les itérateurs d'accès aléatoire, ses performances sont supérieures avec les itérateurs d'accès aléatoire.

Exemple

L'exemple suivant illustre la structure de base de l'algorithme parallel_for_each. Cet exemple imprime sur la console chaque valeur dans un objet std::array en parallèle.

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

Cet exemple génère l'exemple de sortie suivant :

4 5 1 2 3

Étant donné que l'algorithme parallel_for_each agit sur chaque élément en parallèle, l'ordre dans lequel les valeurs sont imprimées sur la console varie.

Pour obtenir un exemple complet utilisant l'algorithme parallel_for_each, consultez Comment : écrire une boucle parallel_for_each.

[retour en haut]

Algorithme parallel_invoke

L'algorithme Concurrency::parallel_invoke exécute un ensemble de tâches en parallèle. Il retourne des valeurs une fois que chaque tâche est terminée. Cet algorithme est utile lorsque vous voulez exécuter simultanément plusieurs tâches indépendantes.

L'algorithme parallel_invoke prend comme paramètres une série de fonctions de travail (fonctions lambda, objets de fonction ou pointeurs fonction). L'algorithme parallel_invoke est surchargé pour prendre entre deux et dix paramètres. Chaque fonction que vous passez à parallel_invoke doit prendre les paramètres nuls.

Comme d'autres algorithmes parallèles, parallel_invoke n'exécute pas les tâches dans un ordre spécifique. La rubrique Parallélisme des tâches (runtime d'accès concurrentiel) illustre le rapport entre l'algorithme parallel_invoke et les tâches et groupes de tâches.

Exemple

L'exemple suivant illustre la structure de base de l'algorithme parallel_invoke. Cet exemple appelle simultanément la fonction twice sur trois variables locales et affiche le résultat sur la 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;
}

Cet exemple produit la sortie suivante :

108 11.2 HelloHello

Pour obtenir des exemples complets utilisant l'algorithme parallel_invoke, consultez Comment : utiliser parallel_invoke pour écrire une routine de tri parallèle et Comment : utiliser parallel_invoke pour exécuter des opérations parallèles.

[retour en haut]

Rubriques connexes

Référence

parallel_for, fonction

parallel_for_each, fonction

parallel_invoke, fonction