Avec Java 8 et lambdas, il est facile d'itérer les collections en tant que flux et tout aussi facile d'utiliser un flux parallèle. Deux exemples tirés de la documentation , 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 la commande, serait-il toujours avantageux 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 utiliser le flux parallèle et quand utiliser le non parallèle?
(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.)
la source
Runnable
que j'appellestart()
pour les utiliser commeThreads
, est-ce correct de changer cela en utilisant des flux java 8 dans un.forEach()
parallélisé? Ensuite, je serais en mesure de retirer le code de thread de la classe. Mais y a-t-il des inconvénients?L'API Stream a été conçue pour faciliter l'écriture de calculs d'une manière qui soit abstraite de la façon dont ils seraient exécutés, facilitant ainsi le passage du séquentiel au parallèle.
Cependant, ce n'est pas parce que c'est facile, 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 avantage autre que la possibilité d'une exécution plus rapide lorsque davantage de cœurs 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 sur plusieurs processeurs; si cela se produit réellement dépend de beaucoup de choses, y compris la taille de votre ensemble de données, la quantité de calcul que vous faites sur chaque élément, la nature du calcul (en particulier, le traitement d'un élément interagit-il avec le traitement des autres?) , le nombre de processeurs disponibles et le nombre d'autres tâches en concurrence pour ces processeurs.
De plus, notez que le parallélisme expose également souvent le non-déterminisme dans le calcul qui est souvent caché par les 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 non, et parfois même le ralentira. Il est préférable de développer d'abord en utilisant l'exécution séquentielle, puis d'appliquer le parallélisme là où
(A) vous savez qu'il y a effectivement un avantage à augmenter les performances et
(B) qu'il fournira réellement des performances accrues.
(A) est un problème commercial et non technique. Si vous êtes un expert en performances, vous pourrez généralement consulter le code et déterminer (B), mais le chemin intelligent est de mesurer. (Et, ne vous embêtez 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 en termes de performances. Pour un problème à 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 à Q plus élevé, vous verrez des points morts à des seuils inférieurs.
Mais la réalité est assez compliquée. Donc, jusqu'à ce que vous atteigniez l'expertise, identifiez d'abord quand le traitement séquentiel vous coûte réellement quelque chose, puis mesurez si le parallélisme vous aidera.
la source
findAny
place defindFirst
...myListOfURLs.stream().map((url) -> downloadPage(url))...
).ForkJoinPool.commonPool()
et vous ne voulez pas que les tâches de blocage y soient effectuées.J'ai regardé l'une des présentations de Brian Goetz (Java Language Architect et responsable des spécifications pour Lambda Expressions) . 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 est plus cher que de simplement faire le travail!
Coûts de répartition / gestion des tâches
- Peut faire beaucoup de travail dans le temps qu'il faut pour remettre le travail à un autre thread.
Coûts de combinaison des résultats
- Parfois, la combinaison implique la copie de nombreuses données. Par exemple, l'ajout de numéros est bon marché tandis que la fusion d'ensembles coûte cher.
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, alors vous ne gagneriez rien à la parallélisation. C'est pourquoi les sources basées sur des tableaux se parallélisent le mieux car les prochains index (proches de l'index actuel) sont mis en cache et il y a moins de chances que le CPU subisse un échec de cache.
Il mentionne également une formule relativement simple pour déterminer une chance d'accélération parallèle.
Modèle NQ :
où,
N = nombre d'éléments de données
Q = quantité de travail par élément
la source
JB a frappé le clou sur la tête. La seule chose que je peux ajouter, c'est que Java 8 ne fait pas de traitement parallèle pur, il fait du paraquentiel . Oui, j'ai écrit l'article et je fais du F / J depuis trente ans, donc je comprends le problème.
la source
ArrayList
/HashMap
.D'autres réponses ont déjà couvert le profilage pour éviter une optimisation prématurée et des frais généraux dans le traitement parallèle. Cette réponse explique le choix idéal des structures de données pour le streaming parallèle.
Source: article n ° 48 Soyez prudent lorsque vous créez des flux parallèles, efficace Java 3e par Joshua Bloch
la source
Ne parallélisez jamais un flux infini avec une limite. Voici ce qui se passe:
Résultat
Même si vous utilisez
.limit(...)
Explication ici: Java 8, l'utilisation de .parallel dans un flux provoque une erreur OOM
De même, n'utilisez pas parallèle si le flux est ordonné et contient beaucoup plus d'éléments que vous souhaitez traiter, par exemple
Cela peut s'exécuter beaucoup plus longtemps car les threads parallèles peuvent fonctionner sur de nombreuses plages de numéros au lieu de la plage cruciale 0-100, ce qui peut prendre très longtemps.
la source