Почему Collection.parallelStream() существует, когда .stream(). Parallel() делает то же самое?

В Java 8 интерфейс Collection был расширен двумя способами, которые возвращают Stream<E>: stream(), который возвращает последовательный поток и parallelStream(), который возвращает возможно параллельный поток. Сам поток также имеет метод parallel(), который возвращает эквивалентный параллельный поток (либо мутирующий текущий поток, либо параллельный, либо создающий новый поток).

У дублирования есть очевидные недостатки:

  • Это сбивает с толку. Вопрос запрашивает ли вызов как parallelStream(). Parallel() необходим, чтобы убедиться, что поток параллелен, учитывая, что parallelStream() может возвращать последовательный поток. Почему parallelStream() существует, если он не может гарантировать гарантию? Другой путь также запутан - если parallelStream() возвращает последовательный поток, возможно, есть причина (например, по сути последовательная структура данных, для которой параллельные потоки являются ловушкой производительности); что должен сделать Stream.parallel() для такого потока? (UnsupportedOperationException не допускается спецификацией parallel().)

  • Добавление методов к интерфейсу сопряжено с рисками, если существующая реализация имеет аналогично названный метод с несовместимым типом возврата. Добавление parallelStream() в дополнение к stream() удваивает риск небольшого выигрыша. (Обратите внимание, что parallelStream() был в одной точке с именем parallel(), хотя я не знаю, было ли оно переименовано, чтобы избежать конфликтов имен или по другой причине.)

Почему Collection.parallelStream() существует при вызове Collection.stream(). parallel() делает то же самое?

Ответ 1

Javadocs для Collection.(parallelS|s)tream() и Stream сам не отвечает на вопрос, поэтому он отправляется в списки рассылки для обоснования. Я прошел через архивы lambda-libs-spec-наблюдателей и нашел один поток специально о Collection.parallelStream() и еще один поток, который касался того, a href= "http://mail.openjdk.java.net/pipermail/lambda-libs-spec-observers/2013-April/thread.html#1799" rel= "noreferrer" > java.util.Arrays должен предоставить parallelStream() для соответствия (или фактически, следует ли его удалить). Не было разговора "один раз и навсегда", поэтому, возможно, я пропустил что-то из другого списка, или вопрос был урегулирован в частной дискуссии. (Возможно, Брайан Гетц, один из руководителей этого обсуждения, может заполнить все, что не хватает.)

Участники сделали свои баллы хорошо, поэтому этот ответ в основном представляет собой организацию соответствующих цитат с несколькими пояснениями в [скобках], представленными в порядке важности (как я это интерпретирую).

parallelStream() охватывает очень распространенный случай

Brian Goetz в первом потоке, объясняя, почему Collections.parallelStream() является достаточно ценным, чтобы поддерживать даже после того, как другие методы параллельного потока factory были удалены:

У нас нет явных параллельных версий каждого из этих [поточных фабрик]; мы сделали изначально и сократить площадь поверхности API, мы разрезаем их на теория о том, что снижение 20+ методов из API стоило стоимость затухания поверхности и производительность .intRange(...).parallel().   Но мы не сделали этого выбора с помощью Collection.

Мы могли бы либо удалить Collection.parallelStream(), либо добавить параллельные версии всех генераторов, или мы ничего не могли сделать и оставьте его как есть. Я думаю, что все они оправданы на основе дизайна API.

Я вроде как статус-кво, несмотря на его несогласованность. Вместо с использованием методов построения 2N потока, мы имеем N + 1, но этот дополнительный 1 охватывает огромное количество случаев, поскольку оно унаследовано каждым Коллекция. Поэтому я могу оправдать себе, почему с этим дополнительным 1 методом стоит того, и почему принятие несогласованности не идет дальше приемлемым.

Неужели другие не согласны? Является ли N + 1 [Collections.parallelStream() только] практическим выбором здесь? Или мы должны идти для чистоты N [полагаться на Stream.parallel()]? Или удобство и согласованность 2N [параллельных версий всех заводов]? Или там еще лучше N + 3 [Collections.parallelStream() плюс другие специальные случаи], для некоторых других специально выбранных случаев мы хотите оказать особую поддержку?

Брайан Гетц поддерживает эту позицию в более позднем обсуждении Arrays.parallelStream():

Мне по-прежнему нравится Collection.parallelStream; он имеет огромные открытости, и предлагает довольно большой доход от API площадь поверхности - еще один метод, но обеспечивает ценность во многих местах, поскольку коллекция будет действительно обычным случаем источника потока.

parallelStream() более эффективен

Брайан Гетц:

Прямая версия [parallelStream()] более эффективна, поскольку она требует меньше обертывания (для превратите поток в параллельный поток, вы должны сначала создать последовательный поток, затем передать право собственности на свое государство в новое Поток.) ​​

В ответ на скептицизм Кевина Бурриллиона о том, является ли эффект значительным, Брайан снова:

Зависит от того, насколько серьезно вы рассчитываете. Дуг считает индивидуальный объект создания и виртуальные вызовы на пути к параллельной операции, потому что, пока вы не начнете разворачивать, вы на неправильной стороне Амдаля закон - это все "серийная фракция", которая происходит, прежде чем вы сможете разветкить любая работа, которая еще больше увеличивает ваш порог безубыточности. Таким образом, получение путь настройки для параллельных операций быстро ценен.

Дуг Лиа следует за, но хеджирует свою позицию:

Люди, занимающиеся параллельной библиотечной поддержкой, нуждаются в некотором отношении настройка таких вещей. На машине, которая скоро станет типичной, каждый цикл, который вы тратите на настройку parallelism, стоит сказать 64 цикла. Вероятно, у вас была бы другая реакция, если бы требовалось 64 для создания параллельных вычислений.

Тем не менее, я всегда полностью поддерживаю принуждение разработчиков работать более интенсивно ради лучших API-интерфейсов, если API не исключают эффективной реализации. Так что, если убить parallelStream действительно важно, мы найдем способ поверните stream().parallel() в бит-флип или немного.

В самом деле, более поздняя дискуссия о Arrays.parallelStream() принимает к сведению более низкую стоимость Stream.parallel().

stream(). parallel() statefulness усложняет будущее

Во время обсуждения переключение потока из последовательного в параллельное и обратно можно чередовать с другими операциями потока. Брайан Гетц, от имени Doug Lea, объясняет, почему последовательное/параллельное переключение режимов может осложнить будущую разработку платформы Java:

Я возьму свой лучший удар, объяснив, почему: потому что он (например, методы (сортировка, различие, предел)), которые вам также не нравятся, переместите нас постепенно дальше от возможности выразить поточные трубопроводы в терминов традиционных параллельных конструкций данных, что дополнительно ограничивает наша способность отображать их непосредственно на завтрашний вычислительный субстрат, будь то векторные процессоры, FPGA, графические процессоры или что-то еще, что мы готовим.

Фильтровать карту-снижать карту [s] очень чисто для всех видов параллельных вычислений субстраты; фильтр-параллельно-карта-последовательной отсортированные предел Параллельной карта-уник-свертка не делает.

Таким образом, весь дизайн API воплощает в себе множество противоречий между его созданием легко выразить вещи, которые пользователь может захотеть выразить, и делать таким образом, что мы можем прогнозировать быстро с прозрачной стоимостью модели.

Переключение этого режима было удалено после дальнейшего обсуждения. В текущей версии библиотеки потоковый конвейер является либо последовательным, либо параллельным; последний вызов sequential()/parallel() выигрывает. Помимо побочного шага проблемы с состоянием, это изменение также улучшило производительность использования parallel() для установки параллельного конвейера из последовательного потока factory.

подвергая parallelStream() как первоклассного гражданина, улучшает восприятие программистом библиотеки, что приводит к написанию лучшего кода

Брайан Гетц снова, в ответ на аргумент Тима Пайерлса, что Stream.parallel() позволяет программистам последовательно понимать потоки, прежде чем идти параллельно:

У меня несколько другая точка зрения о значении этого последовательного интуиция - я рассматриваю повсеместное "последовательное ожидание" как одно, если самые большие проблемы всего этого усилия; люди постоянно приводя их неправильное последовательное смещение, что заставляет их делать глупые такие вещи, как использование одноэлементного массива, как способ "обмануть" "глупый", компилятор, позволяющий им захватывать изменчивый локальный или использовать lambdas как аргументы, чтобы отобразить это состояние мутанта, которое будет использоваться во время вычисление (небезопасным способом), а затем, когда его указали что они делают, отмахиваются и говорят: "Да, но я не делаю это параллельно."

Мы сделали много компромиссов с дизайном, чтобы объединить последовательные и параллельные потоки. Результат, я считаю, является чистым и добавит к возможности библиотеки по-прежнему полезны через 10 лет, но я не особенно, как идея побудить людей думать, что это последовательная библиотека с несколькими параллельными мешками, прибитыми сбоку.