Есть ли какие-либо прямые или косвенные преимущества производительности последовательных потоков Java 8?

$ lookup (aggregation)

Выполняет левое внешнее соединение в незащищенном наборе в той же базе данных для фильтрации в документах из «объединенной» коллекции для обработки. Для каждого входного документа этап $ lookup добавляет новое поле массива, элементы которого являются соответствующими документами из «объединенной» коллекции. Эта стадия $ lookup передает эти измененные документы на следующий этап. Этап $ lookup имеет следующие синтаксисы:

Equality Match

Чтобы выполнить совпадение равенства между полем из входных документов с полем из документов «объединенной» коллекции, этап $ lookup имеет следующий синтаксис:

{
   $lookup:
     {
       from: ,
       localField: ,
       foreignField: ,
       as: 
     }
}

Операция будет соответствовать следующему выражению псевдо-SQL:

SELECT *, 
FROM collection
WHERE  IN (SELECT 
                               FROM 
                               WHERE  );

Mongo URL

3
задан Naman 17 January 2019 в 15:52
поделиться

3 ответа

Потоки могут (и уже имеют некоторые хитрости) под капотом, чего нет в традиционном цикле for. Например:

Arrays.asList(1,2,3)
      .map(x -> x + 1)
      .count();

Начиная с java-9, map будут пропущены, так как вы на самом деле не заботитесь об этом.

Или внутренняя реализация может проверять, отсортирована ли уже определенная структура данных, например:

someSource.stream()
          .sorted()
          ....

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

0
ответ дан Eugene 17 January 2019 в 15:52
поделиться

Если бы вы все еще использовали потоки, вы могли бы создать поток из вашего массива, используя Arrays.stream и использовать forEach как:

Arrays.stream(strings).forEach(s -> {
    System.out.println("Before filtering: " + s);
    if (s.startsWith("a")) {
        System.out.println("After Filtering: " + s);
    }
});

В примечании по производительности, так как вы бы будьте готовы обойти весь массив, нет особой выгоды от использования потоков над циклами. Подробнее об этом было сказано . В Java, каковы преимущества потоков перед циклами? и другие связанные вопросы.

0
ответ дан Naman 17 January 2019 в 15:52
поделиться

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


Вы всегда можете написать ручной цикл, делающий в основном то же самое, что внутренняя реализация Stream. Таким образом, внутренняя оптимизация, как упомянуто в , в этом ответе всегда можно отклонить словами «но я мог бы сделать то же самое в своем цикле».

Но… когда мы сравниваем «поток» с «циклом», действительно ли разумно предположить, что все ручные циклы написаны наиболее эффективным способом для конкретного варианта использования? Конкретная реализация Stream будет применять свои оптимизации ко всем случаям использования, где это применимо, независимо от уровня опыта автора вызывающего кода. Я уже видел циклы, пропускающие возможность короткого замыкания или выполнения избыточных операций, которые не нужны для конкретного случая использования.

Другим аспектом является информация, необходимая для выполнения определенных оптимизаций. Stream API построен на интерфейсе Spliterator , который может предоставлять характеристики исходных данных, например, это позволяет выяснить, имеет ли данные значимый порядок, который необходимо сохранить для определенных операций, или он уже предварительно отсортирован, к естественному порядку или с конкретным компаратором. Он также может предоставить ожидаемое количество элементов, в качестве приблизительного или точного, когда это предсказуемо.

Способу, принимающему произвольный Collection, для реализации алгоритма с обычным циклом, было бы трудно выяснить, существуют ли такие характеристики. List подразумевает осмысленный порядок, тогда как a Set обычно не делает этого, если только это не SortedSet или LinkedHashSet, тогда как последний является конкретным классом реализации, а не интерфейсом. Таким образом, при проверке всех известных группировок все еще могут отсутствовать реализации сторонних производителей со специальными контрактами, которые нельзя выразить с помощью предварительно определенного интерфейса.

Конечно, начиная с Java 8, вы могли бы приобрести Spliterator самостоятельно, чтобы изучить эти характеристики, но это изменило бы ваше решение цикла на нетривиальную вещь, а также подразумевало бы повторение работы, уже проделанной с Stream API.


Существует еще одно интересное отличие между потоковыми решениями на основе Spliterator и обычными циклами, использующими Iterator при выполнении итерации по чему-то другому, чем массив. Шаблон должен вызывать hasNext для итератора, за которым следует next, если только hasNext не вернул false. Но контракт Iterator не предписывает эту модель. Вызывающая сторона может вызывать next без hasNext, даже несколько раз, когда известно, что это успешно (например, вы уже знаете размер коллекции). Кроме того, вызывающий абонент может вызывать hasNext несколько раз без next в случае, если вызывающий абонент не запомнил результат предыдущего вызова.

Как следствие, реализации Iterator должны выполнять избыточные операции, например, условие цикла эффективно проверяется дважды, один раз в hasNext, чтобы вернуть boolean, и один раз в next, чтобы бросить NoSuchElementException, когда не выполняется. Часто hasNext должен выполнить фактическую операцию обхода и сохранить результат в экземпляре Iterator, чтобы гарантировать, что результат остается действительным до следующего вызова next. Операция next, в свою очередь, должна проверить, произошел ли такой обход или же она должна выполнить саму операцию. На практике оптимизатор горячей точки может или не может устранить накладные расходы, налагаемые конструкцией Iterator.

Напротив, Spliterator имеет единственный метод обхода, boolean tryAdvance(Consumer<? super T> action), который выполняет фактическую операцию , и возвращает, был ли элемент. Это значительно упрощает логику цикла. Существует даже void forEachRemaining(Consumer<? super T> action) для операций без короткого замыкания, что позволяет фактической реализации обеспечить всю логику зацикливания. Например, в случае ArrayList операция закончится простым циклом подсчета по индексам, выполняя простой доступ к массиву.

Вы можете сравнить такой дизайн, например, с readLine() из BufferedReader, которое выполняет операцию и возвращает null после последнего элемента, или find() регулярного выражения Matcher, которое выполняет поиск, обновляет состояние сопоставителя и возвращает состояние успеха. [1151 ]

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

0
ответ дан Holger 17 January 2019 в 15:52
поделиться
Другие вопросы по тегам:

Похожие вопросы: