Чем stream отличается от итератора
Перейти к содержимому

Чем stream отличается от итератора

Итератор против потока Java 8

Чтобы воспользоваться широким спектром методов запросов, включенных в java.util.stream Jdk 8, я попытался разработать модели предметной области, в которых получатели отношений с * множественностью (с нулем или более экземплярами) возвращают a Stream<T> , а не Iterable<T> или Iterator<T> .

Я сомневаюсь, что есть ли какие-либо дополнительные накладные расходы по Stream<T> сравнению с Iterator<T> ?

Итак, есть ли какой-либо недостаток в компрометации моей модели предметной области с помощью Stream<T> ?

Или вместо этого я должен всегда возвращать Iterator<T> или Iterable<T> и оставлять за конечным пользователем решение о том, использовать ли поток или нет, путем преобразования этого итератора с помощью StreamUtils ?

Обратите внимание , что возврат Collection не является допустимым вариантом, поскольку в этом случае большинство отношений являются ленивыми и имеют неизвестный размер.

Здесь много советов по производительности, но, к сожалению, большая часть из них является догадками, и мало что указывает на реальные соображения производительности.

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

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

Существуют некоторые дополнительные фиксированные накладные расходы при запуске по сравнению с созданием еще нескольких объектов, прежде чем вы начнете вычислять. Если ваш набор данных велик, это не имеет значения; это небольшие начальные затраты, которые амортизируются за счет большого количества вычислений. (И если ваш набор данных небольшой, это, вероятно, также не имеет значения, потому что, если ваша программа работает с небольшими наборами данных, производительность, как правило, также не является вашей главной заботой.) Где это имеет значение? Stream Iterator дело в параллельности; любое время, затрачиваемое на настройку конвейера, превращается в порядковую дробь закона Амдала; если вы посмотрите на реализацию, мы прилагаем все усилия, чтобы уменьшить количество объектов во время настройки потока, но я был бы рад найти способы уменьшить его, поскольку это напрямую влияет на размер набора данных безубыточности, где параллель начинает побеждать. последовательный.

Но более важной, чем фиксированная стоимость запуска, является стоимость доступа к каждому элементу. Здесь потоки на самом деле выигрывают — и часто выигрывают по-крупному — что некоторых может удивить. (В наших тестах производительности мы обычно видим потоковые конвейеры, которые могут превосходить по производительности свои Collection аналоги с циклом for.) И этому есть простое объяснение: Spliterator имеет принципиально более низкие затраты на доступ к каждому элементу, чем Iterator , даже последовательно. На это есть несколько причин.

Протокол Iterator принципиально менее эффективен. Для получения каждого элемента требуется вызвать два метода. Кроме того, поскольку итераторы должны быть устойчивыми к таким вещам, как вызовы next() без hasNext() , или hasNext() несколько раз без next() , оба этих метода обычно должны выполнять некоторое защитное кодирование (и, как правило, больше состояния и ветвления), что увеличивает неэффективность. С другой стороны, даже медленный способ обхода разделителя ( tryAdvance ) не несет этой нагрузки. (Это еще хуже для параллельных структур данных, потому что двойственность next / hasNext в корне неприемлема, и Iterator реализации должны выполнять больше работы для защиты от параллельных модификаций, чем Spliterator реализации.)

Spliterator дополнительно предлагает итерацию «быстрого пути» forEachRemaining , которую можно использовать большую часть времени (сокращение, forEach), еще больше уменьшая накладные расходы кода итерации, который обеспечивает доступ к внутренним структурам структуры данных. Это также имеет тенденцию очень хорошо встраиваться, что, в свою очередь, повышает эффективность других оптимизаций, таких как движение кода, исключение проверки границ и т. д.

Кроме того, обход через Spliterator обычно требует намного меньше операций записи кучи, чем с Iterator . При Iterator использовании каждый элемент вызывает одну или несколько операций записи кучи (если только элемент не Iterator может быть масштабирован с помощью escape-анализа и его полей, поднятых в регистры). Помимо других проблем, это вызывает активность маркировки карты GC, что приводит к конфликту строк кэша для меток карты. С другой стороны, Spliterators как правило, имеют меньше состояния, а промышленные forEachRemaining реализации имеют тенденцию откладывать запись чего-либо в кучу до конца обхода, вместо этого сохраняя состояние итерации в локальных переменных, которые естественным образом сопоставляются с регистрами, что приводит к снижению активности шины памяти. .

Резюме: не волнуйтесь, будьте счастливы. Spliterator лучше Iterator , даже без параллелизма. (Кроме того, их, как правило, просто легче написать и сложнее ошибиться.)

Java streams vs Iterators

I have been playing with the new and shiny functional part of Java and one of the things that puzzle me the most are streams?

What is their use?

On Google I mostly found explanations of how to use them and practical examples, which I already got down, nothing concrete about the magic behind the scenes, which is what interests me.

I dont mean in a practical sense, coming from a few functional languages i figured out map/filter/reduce/etc. fairly quickly but why do we need to convert to a stream first? Java already has iterators. Is there a fundamental difference between stream and iterator like one being lazy and the other not? Or is it something else?

Bottom line: what is the fundamental difference between iterators and streams and what functionality couldn’t be implemented as an extension to iterators and needed a whole new family of types?

user avatar

5 Answers 5

Talking about streams, in general, is a vast topic. However, I will derive about why you should favour the streams API over Iterators.

First and foremost, with the stream API, we can now program at a much higher level of abstraction, just like SQL queries, i.e. we express what we want and let the library handle the rest.

Second, stream operations perform their iterations behind the scenes (internal iteration) , this means the processing of the data could be done in parallel or in a different order that may be more optimized.

On the other hand, if you decide to explicitly iterate over your collection to perform some computation whether that’s with an Iterator or the syntactic sugar for an iterator (the enhanced for loop) then you’re explicitly taking the items in the collection and processing them one by one thus it’s inherently serial.

Using iterators instead of the stream API also means a lot more work has to be done when you want to go parallel or find different ways to optimise your program.

Yet, this also means that you’re spending much more time dealing with the low-level details instead of just focusing on what you want your program to do.

Also mentioned in the Java-8 in Action book:

The internal iteration in the Streams library can automatically choose a data representation and implementation of parallelism to match your hardware. By contrast, once you’ve chosen external iteration by writing for-each, then you’ve essentially committed to self-manage any parallelism. (Self-managing in practice means either “one fine day we’ll parallelize this” or “starting the long and arduous battle involving tasks and synchronized”.)

Java 8 needed an interface like Collection but without iterators, ergo Stream!

Essentially, with the stream API, your life is much easier in many ways but what I find most useful is the fact that you can now put more time into focusing on what you want your code to do and at the same time you can decide to go parallel without dealing with the low-level stuff.

This is of course not saying to always utilise streams wherever/whenever possible. Rather it’s stating the benefits of using streams over Iterators.

There are certain places where it will be more appropriate to use Iterators rather than the stream API and vice versa. So choose wisely which approach to proceed with in terms of processing data in collections.

Итератор против потока Java 8

чтобы воспользоваться широким спектром методов запроса включенными в java.util.stream Jdk 8 я пытаюсь разработать модели доменов, где геттеры отношений с * кратность (с нулем или более экземпляров ) возвращает Stream<T> , вместо Iterable<T> или Iterator<T> .

я сомневаюсь, есть ли какие-либо дополнительные накладные расходы, понесенные Stream<T> по сравнению с Iterator<T> ?

Итак, есть ли какой-либо недостаток в компрометации моей модели домена с помощью Stream<T> ?

или вместо этого я должен всегда возвращать Iterator<T> или Iterable<T> , и оставьте конечному пользователю решение о том, использовать ли поток или нет, преобразовав этот итератор с помощью StreamUtils ?

Примечание что возврат Collection не является допустимым вариантом, потому что в этом случае большинство отношений ленивы и с неизвестным размером.

2 ответов

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

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

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

есть некоторые дополнительные фиксированные накладные расходы при запуске создания a Stream по сравнению с созданием Iterator — еще несколько объектов, прежде чем начать вычисления. Если ваш набор данных большой, это не имеет значения; это небольшая стоимость запуска, амортизированная за много вычислений. (И если ваш набор данных мал, это, вероятно, также не имеет значения — потому что если ваша программа работает на небольших наборах данных, производительность, как правило, не ваша забота #1 либо.) Где это тут дело в том, когда идет параллельно; любое время, потраченное на настройку конвейера, переходит в последовательную часть закона Amdahl; если вы посмотрите на реализацию, мы упорно работаем над тем, чтобы во время настройки потока объект считался, но я был бы рад найти способы уменьшить его, так как это напрямую влияет на размер набора данных безубыточности, где параллель начинает выигрывать по порядку.

но более важной, чем фиксированная стоимость запуска, является стоимость доступа к каждому элементу. Здесь потоки на самом деле выигрывают-и часто выигрывают по-крупному-что некоторых может удивить. (В наших тестах производительности мы обычно видим трубопроводы потока, которые могут превзойти их for-loop over Collection коллегами.) И есть простое объяснение этому: Spliterator имеет фундаментально более низкие цены доступа в-элемента чем Iterator , даже последовательно. Существует несколько причин этот.

протокол итератора принципиально менее эффективен. Для получения каждого элемента требуется вызвать два метода. Далее, потому что итераторы должны быть надежными для таких вещей, как вызов next() без hasNext() или hasNext() несколько раз без next() , оба этих метода обычно должны выполнять некоторое защитное кодирование (и, как правило, больше статичности и ветвления), что добавляет неэффективности. С другой стороны, даже медленный способ пересечь spliterator ( tryAdvance ) не имеет такого бремени. (Это еще хуже для параллельных структур данных, потому что next / hasNext двойственность в основе своей колоритна, и Iterator реализации должны выполнять больше работы для защиты от параллельных модификаций, чем Spliterator реализаций.)

Spliterator далее предлагает итерацию «быстрого пути»— forEachRemaining — который можно использовать большую часть времени (уменьшение, forEach), дальнейшее уменьшение накладных расходов кода итерации, который обеспечивает доступ к внутренней структуре данных. Это также очень хорошо встроено, что, в свою очередь, повышает эффективность других оптимизаций, таких как движение кода, устранение проверки границ и т. д.

далее, проходящего через Spliterator как правило, гораздо меньше кучи пишет, чем с Iterator . С Iterator , каждый элемент вызывает одну или несколько записей кучи (если только Iterator может быть scalarized через Escape-анализ и подсадил в регистры.) Среди другие проблемы, это вызывает активность метки карты GC, что приводит к конфликту строк кэша для меток карты. С другой стороны,—20— > клонат иметь меньше государства, и промышленн-прочности forEachRemaining реализации, как правило, откладывают запись чего-либо в кучу до конца обхода, вместо этого сохраняя ее состояние итерации в локальных файлах, которые естественно сопоставляются с регистрами, что приводит к снижению активности шины памяти.

резюме: не волнуйтесь, будьте счастливы. Spliterator — это лучше Iterator , даже без параллельности. (Они также, как правило, просто легче писать и труднее ошибиться.)

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *