Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Parallel LINQ (PLINQ) est une implémentation parallèle du modèle de requêteLanguage-Integrated (LINQ). PLINQ implémente l’ensemble complet d’opérateurs de requête standard LINQ en tant que méthodes d’extension pour l’espace System.Linq de noms et dispose d’opérateurs supplémentaires pour les opérations parallèles. PLINQ combine la simplicité et la lisibilité de la syntaxe LINQ avec la puissance de la programmation parallèle.
Conseil / Astuce
Si vous n’êtes pas familiarisé avec LINQ, il propose un modèle unifié pour interroger une source de données énumérable de manière sécurisée. LINQ to Objects est le nom des requêtes LINQ exécutées sur des collections en mémoire telles que List<T> des tableaux. Cet article suppose que vous avez une compréhension de base de LINQ. Pour plus d’informations, consultez Language-Integrated Requête (LINQ).
Qu’est-ce qu’une requête parallèle ?
Une requête PLINQ de plusieurs façons ressemble à une requête LINQ to Objects non parallèle. Les requêtes PLINQ, comme les requêtes LINQ séquentielles, fonctionnent sur n’importe quelle source de données ou IEnumerable<T> en mémoire IEnumerable et ont une exécution différée, ce qui signifie qu’elles ne commencent pas à s’exécuter tant que la requête n’est pas énumérée. La principale différence est que PLINQ tente d’utiliser pleinement tous les processeurs sur le système. Pour ce faire, il partitionne la source de données en segments, puis exécute la requête sur chaque segment sur des threads de travail distincts en parallèle sur plusieurs processeurs. Dans de nombreux cas, l’exécution parallèle signifie que la requête s’exécute beaucoup plus rapidement.
Grâce à l’exécution parallèle, PLINQ peut obtenir des améliorations significatives des performances sur le code hérité pour certains types de requêtes, souvent en ajoutant l’opération AsParallel de requête à la source de données. Toutefois, le parallélisme peut introduire ses propres complexités, et toutes les opérations de requête ne s’exécutent pas plus rapidement dans PLINQ. En fait, la parallélisation ralentit réellement certaines requêtes. Par conséquent, vous devez comprendre comment les problèmes tels que l’ordre affectent les requêtes parallèles. Pour plus d’informations, consultez Comprendre l'accélération dans PLINQ.
Remarque
Cette documentation utilise des expressions lambda pour définir des délégués dans PLINQ. Si vous n’êtes pas familiarisé avec les expressions lambda en C# ou Visual Basic, consultez Expressions lambda dans PLINQ et TPL.
Le reste de cet article donne une vue d’ensemble des principales classes PLINQ et explique comment créer des requêtes PLINQ. Chaque section contient des liens vers des informations plus détaillées et des exemples de code.
La classe ParallelEnumerable
La System.Linq.ParallelEnumerable classe expose presque toutes les fonctionnalités de PLINQ. Il et le reste des types d’espaces de System.Linq noms sont compilés dans l’assembly System.Core.dll. Les projets C# et Visual Basic par défaut dans Visual Studio font référence à l’assembly et importent l’espace de noms.
ParallelEnumerable inclut des implémentations de tous les opérateurs de requête standard pris en charge par LINQ to Objects, bien qu’il ne tente pas de paralléliser chacun d’eux. Si vous n’êtes pas familiarisé avec LINQ, consultez Présentation de LINQ (C#) et Présentation de LINQ (Visual Basic).
Outre les opérateurs de requête standard, la ParallelEnumerable classe contient un ensemble de méthodes qui permettent des comportements spécifiques à l’exécution parallèle. Ces méthodes spécifiques à PLINQ sont répertoriées dans le tableau suivant.
Opérateur ParallelEnumerable | Descriptif |
---|---|
AsParallel | Point d’entrée pour PLINQ. Spécifie que le reste de la requête doit être parallélisé, s’il est possible. |
AsSequential | Spécifie que le reste de la requête doit être exécuté séquentiellement, en tant que requête LINQ non parallèle. |
AsOrdered | Spécifie que PLINQ doit conserver l’ordre de la séquence source pour le reste de la requête, ou jusqu’à ce que l’ordre soit modifié, par exemple par l’utilisation d’une clause orderby (Order By in Visual Basic). |
AsUnordered | Spécifie que PLINQ pour le reste de la requête n’est pas nécessaire pour conserver l’ordre de la séquence source. |
WithCancellation | Spécifie que PLINQ doit surveiller régulièrement l’état du jeton d’annulation fourni et annuler l’exécution si elle est demandée. |
WithDegreeOfParallelism | Spécifie le nombre maximal de processeurs que PLINQ doit utiliser pour paralléliser la requête. |
WithMergeOptions | Fournit un indicateur sur la façon dont PLINQ doit, s’il est possible, fusionner les résultats parallèles en une seule séquence sur le thread consommateur. |
WithExecutionMode | Spécifie si PLINQ doit paralléliser la requête même si le comportement par défaut doit l’exécuter séquentiellement. |
ForAll | Méthode d’énumération multithread qui, contrairement à l’itération sur les résultats de la requête, permet de traiter les résultats en parallèle sans d’abord fusionner vers le thread consommateur. |
Aggregate surcharger | Surcharge qui est unique à PLINQ et active l’agrégation intermédiaire sur les partitions locales de threads, ainsi qu’une fonction d’agrégation finale pour combiner les résultats de toutes les partitions. |
Modèle d’inscription
Lorsque vous écrivez une requête, optez pour PLINQ en appelant la ParallelEnumerable.AsParallel méthode d’extension sur la source de données, comme illustré dans l’exemple suivant.
var source = Enumerable.Range(1, 10000);
// Opt in to PLINQ with AsParallel.
var evenNums = from num in source.AsParallel()
where num % 2 == 0
select num;
Console.WriteLine($"{evenNums.Count()} even numbers out of {source.Count()} total");
// The example displays the following output:
// 5000 even numbers out of 10000 total
Dim source = Enumerable.Range(1, 10000)
' Opt in to PLINQ with AsParallel
Dim evenNums = From num In source.AsParallel()
Where num Mod 2 = 0
Select num
Console.WriteLine("{0} even numbers out of {1} total",
evenNums.Count(), source.Count())
' The example displays the following output:
' 5000 even numbers out of 10000 total
La AsParallel méthode d’extension lie les opérateurs de requête suivants, dans ce cas, where
et select
, aux System.Linq.ParallelEnumerable implémentations.
Modes d’exécution
Par défaut, PLINQ est conservateur. Au moment de l’exécution, l’infrastructure PLINQ analyse la structure globale de la requête. Si la requête est susceptible de générer des accélérations par parallélisation, PLINQ partitionne la séquence source en tâches qui peuvent être exécutées simultanément. S’il n’est pas sûr de paralléliser une requête, PLINQ exécute simplement la requête de manière séquentielle. Si PLINQ a le choix entre un algorithme parallèle potentiellement coûteux ou un algorithme séquentiel peu coûteux, il choisit l’algorithme séquentiel par défaut. Vous pouvez utiliser la WithExecutionMode méthode et l’énumération System.Linq.ParallelExecutionMode pour indiquer à PLINQ de sélectionner l’algorithme parallèle. Cela est utile lorsque vous savez en testant et en mesure qu’une requête particulière s’exécute plus rapidement en parallèle. Pour plus d’informations, consultez Comment : spécifier le mode d’exécution dans PLINQ.
Degré de parallélisme
Par défaut, PLINQ utilise tous les processeurs sur l’ordinateur hôte. Vous pouvez demander à PLINQ d’utiliser pas plus qu’un nombre spécifié de processeurs à l’aide de la WithDegreeOfParallelism méthode. Cela est utile lorsque vous souhaitez vous assurer que d’autres processus s’exécutant sur l’ordinateur reçoivent un certain temps processeur. L’extrait de code suivant limite la requête à l’utilisation d’un maximum de deux processeurs.
var query = from item in source.AsParallel().WithDegreeOfParallelism(2)
where Compute(item) > 42
select item;
Dim query = From item In source.AsParallel().WithDegreeOfParallelism(2)
Where Compute(item) > 42
Select item
Dans les cas où une requête effectue une quantité importante de travail non lié au calcul, comme les E/S de fichier, il peut être utile de spécifier un degré de parallélisme supérieur au nombre de cœurs sur l’ordinateur.
Requêtes parallèles ordonnées ou non ordonnées
Dans certaines requêtes, un opérateur de requête doit produire des résultats qui conservent l’ordre de la séquence source. PLINQ fournit l’opérateur AsOrdered à cet effet. AsOrdered est distinct de AsSequential. Une AsOrdered séquence est toujours traitée en parallèle, mais ses résultats sont mis en mémoire tampon et triés. Étant donné que la conservation de l’ordre implique généralement un travail supplémentaire, une AsOrdered séquence peut être traitée plus lentement que la séquence par défaut AsUnordered . Si une opération parallèle ordonnée particulière est plus rapide qu’une version séquentielle de l’opération dépend de nombreux facteurs.
L’exemple de code suivant montre comment opter pour la conservation de l’ordre.
var evenNums =
from num in numbers.AsParallel().AsOrdered()
where num % 2 == 0
select num;
Dim evenNums = From num In numbers.AsParallel().AsOrdered()
Where num Mod 2 = 0
Select num
Pour plus d’informations, consultez La préservation de l’ordre dans PLINQ.
Requêtes parallèles et séquentielles
Certaines opérations nécessitent que les données sources soient remises de manière séquentielle. Les ParallelEnumerable opérateurs de requête rétablissent automatiquement le mode séquentiel lorsqu’il est nécessaire. Pour les opérateurs de requête définis par l’utilisateur et les délégués utilisateur qui nécessitent une exécution séquentielle, PLINQ fournit la AsSequential méthode. Lorsque vous utilisez AsSequential, tous les opérateurs suivants dans la requête sont exécutés séquentiellement jusqu’à ce qu’ils AsParallel soient appelés à nouveau. Pour plus d’informations, consultez Guide pratique pour combiner des requêtes LINQ parallèles et séquentielles.
Options de fusion des résultats de requête
Lorsqu’une requête PLINQ s’exécute en parallèle, ses résultats de chaque thread de travail doivent être fusionnés sur le thread principal pour la consommation par une foreach
boucle (For Each
en Visual Basic) ou l’insertion dans une liste ou un tableau. Dans certains cas, il peut être utile de spécifier un type particulier d’opération de fusion, par exemple, pour commencer à produire des résultats plus rapidement. À cet effet, PLINQ prend en charge la WithMergeOptions méthode et l’énumération ParallelMergeOptions . Pour plus d’informations, consultez Options de fusion dans PLINQ.
Opérateur ForAll
Dans les requêtes LINQ séquentielles, l’exécution est différée jusqu’à ce que la requête soit énumérée dans une foreach
boucle (For Each
en Visual Basic) ou en appelant une méthode telle que ToList , ToArray ou ToDictionary. Dans PLINQ, vous pouvez également utiliser foreach
pour exécuter la requête et effectuer une itération dans les résultats. Toutefois, foreach
elle ne s’exécute pas en parallèle et nécessite donc que la sortie de toutes les tâches parallèles soit fusionnée dans le thread sur lequel la boucle est en cours d’exécution. Dans PLINQ, vous pouvez utiliser foreach
quand vous devez conserver l’ordre final des résultats de la requête, ainsi que chaque fois que vous traitez les résultats d’une manière série, par exemple lorsque vous appelez Console.WriteLine
chaque élément. Pour accélérer l’exécution des requêtes lorsque la conservation de l’ordre n’est pas nécessaire et que le traitement des résultats peut lui-même être parallélisé, utilisez la ForAll méthode pour exécuter une requête PLINQ.
ForAll n’effectue pas cette dernière étape de fusion. L’exemple de code suivant montre comment utiliser la ForAll méthode.
System.Collections.Concurrent.ConcurrentBag<T> est utilisé ici, car il est optimisé pour plusieurs threads ajoutés simultanément sans tenter de supprimer des éléments.
var nums = Enumerable.Range(10, 10000);
var query =
from num in nums.AsParallel()
where num % 10 == 0
select num;
// Process the results as each thread completes
// and add them to a System.Collections.Concurrent.ConcurrentBag(Of Int)
// which can safely accept concurrent add operations
query.ForAll(e => concurrentBag.Add(Compute(e)));
Dim nums = Enumerable.Range(10, 10000)
Dim query = From num In nums.AsParallel()
Where num Mod 10 = 0
Select num
' Process the results as each thread completes
' and add them to a System.Collections.Concurrent.ConcurrentBag(Of Int)
' which can safely accept concurrent add operations
query.ForAll(Sub(e) concurrentBag.Add(Compute(e)))
L’illustration suivante montre la différence entre foreach
et ForAll en ce qui concerne l’exécution des requêtes.
Annulation
PLINQ est intégré aux types d’annulation dans .NET. (Pour plus d’informations, consultez Annulation dans les threads managés.) Par conséquent, contrairement aux requêtes LINQ to Objects séquentielles, les requêtes PLINQ peuvent être annulées. Pour créer une requête PLINQ annulable, utilisez l’opérateur WithCancellation sur la requête et fournissez une CancellationToken instance comme argument. Lorsque la IsCancellationRequested propriété sur le jeton est définie sur true, PLINQ le remarque, arrête le traitement sur tous les threads et lève un OperationCanceledException.
Il est possible qu’une requête PLINQ continue à traiter certains éléments une fois le jeton d’annulation défini.
Pour une plus grande réactivité, vous pouvez également répondre aux demandes d’annulation dans les délégués utilisateur de longue durée. Pour plus d’informations, consultez Comment : annuler une requête PLINQ.
Exceptions
Lorsqu’une requête PLINQ s’exécute, plusieurs exceptions peuvent être levées simultanément à partir de différents threads. En outre, le code permettant de gérer l’exception peut se trouver sur un thread différent du code qui a levé l’exception. PLINQ utilise le AggregateException type pour encapsuler toutes les exceptions levées par une requête et marshaler ces exceptions vers le thread appelant. Sur le thread appelant, un seul bloc try-catch est requis. Toutefois, vous pouvez effectuer une itération à travers toutes les exceptions encapsulées dans le AggregateException fichier et intercepter celles à partir de lesquelles vous pouvez récupérer en toute sécurité. Dans de rares cas, certaines exceptions peuvent être levées qui ne sont pas encapsulées dans un AggregateException, et ThreadAbortExceptions ne sont pas également encapsulées.
Lorsque des exceptions sont autorisées à remonter dans le thread de jointure, il est possible qu’une requête continue à traiter certains éléments une fois l’exception levée.
Pour plus d’informations, consultez Guide pratique pour gérer les exceptions dans une requête PLINQ.
Partitionneurs personnalisés
Dans certains cas, vous pouvez améliorer les performances des requêtes en écrivant un partitionneur personnalisé qui tire parti de certaines caractéristiques des données sources. Dans la requête, le partitionneur personnalisé lui-même est l’objet énumérable interrogé.
int[] arr = new int[9999];
Partitioner<int> partitioner = new MyArrayPartitioner<int>(arr);
var query = partitioner.AsParallel().Select(SomeFunction);
Dim arr(10000) As Integer
Dim partitioner As Partitioner(Of Integer) = New MyArrayPartitioner(Of Integer)(arr)
Dim query = partitioner.AsParallel().Select(Function(x) SomeFunction(x))
PLINQ prend en charge un nombre fixe de partitions (bien que les données puissent être réaffectées dynamiquement à ces partitions pendant l’exécution pour l’équilibrage de charge). For et ForEach ne prennent en charge que le partitionnement dynamique, ce qui signifie que le nombre de partitions change au moment de l’exécution. Pour plus d’informations, consultez Partitionneurs personnalisés pour PLINQ et TPL.
Mesure des performances PLINQ
Dans de nombreux cas, une requête peut être parallélisée, mais la surcharge de configuration de la requête parallèle l’emporte sur les avantages de performances gagnés. Si une requête n’effectue pas beaucoup de calcul ou si la source de données est petite, une requête PLINQ peut être plus lente qu’une requête LINQ to Objects séquentielle. Vous pouvez utiliser Parallel Performance Analyzer dans Visual Studio Team Server pour comparer les performances de différentes requêtes, localiser les goulots d’étranglement de traitement et déterminer si votre requête s’exécute en parallèle ou séquentiellement. Pour plus d’informations, consultez Visualiseur concurrentiel et Guide pratique pour mesurer les performances des requêtes PLINQ.