Dois-je toujours utiliser un flux parallèle lorsque cela est possible?


Avec Java 8 et lambdas, il est facile d'itérer sur des collections en tant que flux, et tout aussi facile d'utiliser un flux parallèle. Deux exemples de les docs , le second utilisant parallelStream:

myShapesCollection.stream()
    .filter(e -> e.getColor() == Color.RED)
    .forEach(e -> System.out.println(e.getName()));

myShapesCollection.parallelStream() // <-- This one uses parallel
    .filter(e -> e.getColor() == Color.RED)
    .forEach(e -> System.out.println(e.getName()));

Tant que je ne me soucie pas de l'ordre, serait - il toujours bénéfique d'utiliser le parallèle? On pourrait penser qu'il est plus rapide de diviser le travail sur plus de cœurs.

Y a-t-il d'autres considérations? Quand le flux parallèle doit-il être utilisé et quand le non parallèle doit-il l'être utilisé?

(Cette question est posée pour déclencher une discussion sur comment et quand utiliser des flux parallèles, pas parce que je pense que toujours les utiliser est une bonne idée.)

Author: Matsemann, 2013-12-04

4 answers

Un flux parallèle a une surcharge beaucoup plus élevée par rapport à un flux séquentiel. Coordonner les fils prend beaucoup de temps. J'utiliserais des flux séquentiels par défaut et ne considérerais que les flux parallèles si

  • J'ai une énorme quantité d'éléments à traiter (ou le traitement de chaque élément prend du temps et est parallélisable)

  • J'ai un problème de performance, en premier lieu

  • Je n'exécute pas déjà le processus dans un multi-thread environnement (par exemple: dans un conteneur web, si j'ai déjà beaucoup de requêtes à traiter en parallèle, l'ajout d'une couche supplémentaire de parallélisme à l'intérieur de chaque requête pourrait avoir des effets plus négatifs que positifs)

Dans votre exemple, les performances seront de toute façon entraînées par l'accès synchronisé à System.out.println(), et rendre ce processus parallèle n'aura aucun effet, voire négatif.

De plus, rappelez-vous que les flux parallèles ne résolvent pas comme par magie tous les problèmes de synchronisation. Si une ressource partagée est utilisée par les prédicats et les fonctions utilisés dans le processus, vous devrez vous assurer que tout est thread-safe. En particulier, les effets secondaires sont des choses dont vous devez vraiment vous soucier si vous allez en parallèle.

En tout cas, mesurez, ne devinez pas! Seule une mesure vous dira si le parallélisme en vaut la peine ou non.

 500
Author: JB Nizet, 2017-05-10 03:07:16

L'API Stream a été conçue pour faciliter l'écriture de calculs d'une manière qui a été abstraite loin de la façon dont ils seraient exécutés, ce qui rend la commutation entre séquentielle et parallèle facile.

Cependant, juste parce que c'est facile, ne signifie pas que c'est toujours une bonne idée, et en fait, c'est une mauvaise idée de simplement laisser tomber .parallel() partout simplement parce que vous le pouvez.

Tout d'abord, notez que le parallélisme n'offre aucun autre avantage que la possibilité d'une exécution plus rapide lorsque plus les noyaux sont disponibles. Une exécution parallèle impliquera toujours plus de travail qu'une exécution séquentielle, car en plus de résoudre le problème, elle doit également effectuer la répartition et la coordination des sous-tâches. L'espoir est que vous serez en mesure d'obtenir la réponse plus rapidement en répartissant le travail entre plusieurs processeurs; que cela se produise réellement dépend de beaucoup de choses, y compris la taille de votre ensemble de données, combien de calculs vous faites sur chaque élément, la nature du calcul (plus précisément, le traitement d'un élément d'interagir avec le traitement des autres?), le nombre de processeurs disponibles, et le nombre d'autres tâches concurrentes pour ces processeurs.

En outre, notez que le parallélisme expose également souvent le non déterminisme dans le calcul qui est souvent caché par des implémentations séquentielles; parfois, cela n'a pas d'importance, ou peut être atténué en contraignant les opérations impliquées (c'est-à-dire que les opérateurs de réduction doivent être sans état et associatifs.)

En réalité, parfois le parallélisme accélérera votre calcul, parfois il ne le fera pas, et parfois il le ralentira même. Il est préférable de développer d'abord en utilisant l'exécution séquentielle, puis d'appliquer le parallélisme lorsque (A) vous savez qu'il y a réellement un avantage à une augmentation des performances et (B) qu'il offrira en fait une augmentation des performances. A) est un problème commercial et non technique. Si vous êtes un expert en performance, vous pourrez généralement consulter le code et déterminer (B), mais le chemin intelligent est de mesurer. (Et, ne vous embêtez même pas jusqu'à ce que vous soyez convaincu de (A); si le code est assez rapide, mieux vaut appliquer vos cycles cérébraux ailleurs.)

Le modèle de performance le plus simple pour le parallélisme est le modèle "NQ", où N est le nombre d'éléments et Q est le calcul par élément. En général, vous devez que le produit NQ dépasse un certain seuil avant de commencer à obtenir un avantage de performance. Pour un problème de faible Q comme " additionner des nombres de 1 à N", vous verrez généralement un seuil de rentabilité entre N=1000 et N=10000. Avec des problèmes de Q plus élevés, vous verrez des ruptures à des seuils inférieurs.

Mais la réalité est assez compliquée. Donc, jusqu'à ce que vous atteigniez experthood, identifiez d'abord quand le traitement séquentiel vous coûte réellement quelque chose, puis mesurez si le parallélisme vous aidera.

 176
Author: Brian Goetz, 2018-02-24 16:45:43

J'ai regardé l'un des présentations de Brian Goetz (Langage Java Architecte et spécification de plomb pour les Expressions Lambda). Il explique en détail les 4 points suivants à considérer avant de passer à la parallélisation:

Coûts de fractionnement / décomposition
- Parfois, le fractionnement coûte plus cher que de simplement faire le travail!
Répartition des tâches / coûts de gestion
- Peut faire beaucoup de travail dans le temps qu'il faut pour remettre le travail à un autre fil.
Coûts de combinaison de résultats
- Parfois, la combinaison implique de copier beaucoup de données. Par exemple, l'ajout de nombres est bon marché alors que la fusion d'ensembles est coûteuse.
Localité
– L'éléphant dans la pièce. C'est un point important que tout le monde peut manquer. Vous devriez considérer les échecs de cache, si un CPU attend des données à cause des échecs de cache, vous ne gagneriez rien par parallélisation. C'est pourquoi les sources basées sur des tableaux parallélisent les meilleurs comme les prochains indices (près de l'index actuel) sont mis en cache et il y a moins de chances que le processeur connaisse un manque de cache.

Il mentionne également une formule relativement simple pour déterminer une chance d'accélération parallèle.

NQ Modèle:

N x Q > 10000

où,
N = nombre d'éléments de données
Q = quantité de travail par article

 40
Author: Ram Patra, 2016-08-21 17:40:37

JB a frappé le clou sur la tête. La seule chose que je peux ajouter est que Java8 ne fait pas de traitement parallèle pur, il fait paraquentiel Oui j'ai écrit l'article et je fais F/J depuis trente ans donc je comprends le problème.

 11
Author: edharned, 2013-12-04 19:39:33