С Java 8 и лямбдами легко перебирать коллекции как потоки, и так же просто использовать параллельный поток. Два примера из документов , второй с использованием 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()));
Пока меня не волнует порядок, всегда ли будет выгодно использовать параллель? Казалось бы, быстрее разделить работу на большее количество ядер.
Есть ли другие соображения? Когда следует использовать параллельный поток и когда следует использовать непараллельный?
(Этот вопрос задается для обсуждения того, как и когда использовать параллельные потоки, а не потому, что я думаю, что всегда использовать их - хорошая идея.)
источник
Runnable
что я вызываю,start()
чтобы использовать их какThreads
, это нормально, чтобы изменить это на использование потоков Java 8 в.forEach()
распараллеленном? Тогда я смогу удалить код потока из класса. Но есть ли минусы?Stream API был разработан, чтобы упростить написание вычислений таким способом, который был абстрагирован от способа их выполнения, что облегчало переключение между последовательным и параллельным.
Однако, просто потому, что это легко, это не значит, что это всегда хорошая идея, и на самом деле, это плохая идея - просто бросить
.parallel()
повсюду просто потому, что вы можете.Во-первых, обратите внимание, что параллелизм не дает никаких преимуществ, кроме возможности более быстрого выполнения, когда доступно больше ядер. Параллельное выполнение всегда будет включать больше работы, чем последовательное, потому что в дополнение к решению проблемы, оно также должно выполнять диспетчеризацию и координацию подзадач. Надежда состоит в том, что вы сможете быстрее найти ответ, разбив работу по нескольким процессорам; произойдет ли это на самом деле, зависит от многих вещей, включая размер набора данных, объем вычислений, которые вы выполняете для каждого элемента, характер вычислений (в частности, взаимодействует ли обработка одного элемента с обработкой других?) количество доступных процессоров и число других задач, конкурирующих за эти процессоры.
Кроме того, обратите внимание, что параллелизм также часто выявляет недетерминизм в вычислениях, который часто скрыт последовательными реализациями; иногда это не имеет значения или может быть смягчено путем ограничения задействованных операций (т. е. операторы сокращения должны быть без сохранения состояния и ассоциативными).
В действительности иногда параллелизм ускоряет ваши вычисления, иногда нет, а иногда даже замедляет их. Лучше всего сначала разработать с использованием последовательного выполнения, а затем применить параллелизм, где
(A) вы знаете, что на самом деле есть преимущества для повышения производительности и
(B) что он на самом деле доставит повышенную производительность.
(А) это бизнес-проблема, а не техническая. Если вы эксперт по производительности, вы обычно сможете посмотреть на код и определить (B), но разумный путь - это измерить. (И даже не беспокойтесь, пока не убедитесь в (A); если код достаточно быстрый, лучше применить свои мозговые циклы в другом месте.)
Простейшей моделью производительности для параллелизма является модель «NQ», где N - количество элементов, а Q - вычисление на элемент. Как правило, продукт NQ должен превышать пороговое значение, прежде чем вы начнете получать выигрыш в производительности. Для задачи с низким Q, такой как «сложение чисел от 1 до N», вы обычно видите безубыточность между N = 1000 и N = 10000. При проблемах с более высоким Q вы увидите безубыточности при более низких порогах.
Но реальность довольно сложная. Поэтому, пока вы не достигнете мастерства, сначала определите, когда последовательная обработка вам действительно чего-то стоит, а затем измерьте, поможет ли параллелизм.
источник
findAny
вместоfindFirst
…myListOfURLs.stream().map((url) -> downloadPage(url))...
).ForkJoinPool.commonPool()
и вы не хотите блокировать задачи, чтобы идти туда.Я смотрел одну из презентаций из Брайан Гетц (Java Language Architect & спецификация свинца для лямбда - выражений) . Он подробно объясняет следующие 4 момента, которые следует учитывать перед переходом к распараллеливанию:
Расходы на разделение / разложение.
Иногда разделение обходится дороже, чем просто выполнение работы!
Расходы на диспетчеризацию и управление задачами
- могут выполнять большую работу за время, необходимое для передачи работы другому потоку.
Расходы на комбинирование результатов
- иногда комбинирование включает копирование большого количества данных. Например, добавление чисел дешево, тогда как объединение наборов стоит дорого.
Местность
- Слон в комнате. Это важный момент, который каждый может упустить. Вы должны учитывать пропуски в кеше, если процессор ожидает данные из-за пропусков в кеше, вы ничего не получите от распараллеливания. Вот почему источники на основе массива распараллеливаются лучше всего, так как следующие индексы (рядом с текущим индексом) кэшируются, и вероятность того, что ЦП будет пропускать кэш, будет меньше.
Он также упоминает относительно простую формулу для определения вероятности параллельного ускорения.
Модель NQ :
где
N = количество элементов данных
Q = объем работ на элемент
источник
JB ударил гвоздь по голове. Единственное, что я могу добавить, - это то, что Java 8 не выполняет чисто параллельную обработку, а выполняет параллельную обработку . Да, я написал статью и уже тридцать лет занимаюсь F / J, поэтому понимаю проблему.
источник
ArrayList
/HashMap
.Другие ответы уже охватили профилирование, чтобы избежать преждевременной оптимизации и накладных расходов при параллельной обработке. Этот ответ объясняет идеальный выбор структур данных для параллельной потоковой передачи.
Источник: № 48. Будьте осторожны при создании параллельных, эффективных потоков Java 3e от Джошуа Блоха
источник
Никогда не распараллеливайте бесконечный поток с пределом. Вот что происходит:
Результат
То же самое, если вы используете
.limit(...)
Объяснение здесь: Java 8, использование .parallel в потоке вызывает ошибку OOM
Точно так же, не используйте параллельный, если поток упорядочен и имеет намного больше элементов, чем вы хотите обработать, например
Это может работать намного дольше, потому что параллельные потоки могут работать на множестве диапазонов номеров вместо критического 0-100, в результате чего это займет очень много времени.
источник