Во время обзора кода я предложил улучшить код, связанный с потоками JDK8+. Исходный код выглядел очень похоже, как показано ниже:
Listresult = content.getFancyStuffs().stream() .flatMap(item -> { List objects = new ArrayList<>(); objects.add(item.getElement()); objects.addAll(item.getElements()); return objects.stream(); }) .collect(toList());
Еще несколько подробностей здесь. get Fancy Stuffs()
возвращает список Модных вещей
элементов. Класс Fancy Stuff
содержит два метода получения, где getElement()
возвращает один Элемент
, тогда как getElements()
возвращает (Угадайте, что?) список элементов
.
Интересной частью был лямбда-код, который создает новый список массивов
и добавляет один элемент objects.add(item.getElement ())
и вторая часть, которая добавляет несколько элементов через objects.addAll (item.getElements)
.
Мое предложение, основанное на лучшей читабельности, состояло в том, чтобы вместо этого использовать следующий код:
Listresult = content.getFancyStuffs() .stream() .flatMap(fs -> Stream.concat(Stream.of(fs.getElement()), fs.getElements().stream())) .collect(Collectors.toList());
Пока все хорошо. Но через некоторое время я начал думать о двух решениях. Я спросил себя: какой из них быстрее? Какой из них использует больше памяти? (Обычные вопросы, которые задает разработчик? Не так ли?).
Итак, как бы вы предположили, какое решение является более быстрым? Первый или второй? Я предполагал, что первое решение победит, но основано на некоторых предположениях. Это означает количество элементов, которые проходят через content.getfancystuffs().stream()..
более или менее малы (менее 20), и, кроме того, количество элементов, возвращаемых item.getElements()
также довольно мало (менее 20).
Единственное, что может помочь здесь получить надежный ответ, – это измерить его. Никаких предположений, никаких обоснованных догадок и т. Д. Поэтому я должен провести реальное измерение производительности. Поэтому я создал отдельный проект , который действительно измеряет производительность.
Итак, первая часть кода для измерения производительности выглядит следующим образом:
Решение 1
@Benchmark public Listwith_new_arraylist(Container content) { return content.getFancyStuffs().stream().flatMap(item -> { ArrayList objects = new ArrayList<>(); objects.add(item.getElement()); objects.addAll(item.getElements()); return objects.stream(); }).collect(Collectors.toList()); }
и вторая часть:
Решение 2
@Benchmark public Listwith_stream_concat(Container content) { return content.getFancyStuffs() .stream() .flatMap(fs -> Stream.concat(Stream.of(fs.getElement()), fs.getElements().stream())) .collect(Collectors.toList()); }
при написании приведенного выше кода я подумал о некоторых его частях и получил два других возможных варианта.
Решение 3
Следующий пример, в котором я помещаю элементы непосредственно в конструктор ArrayList
. Это означает, что это может произойти только в более редких случаях, размер списка массивов должен быть изменен, что зависит от количества элементов в item.getElements()
.
@Benchmark public Listwith_new_arraylist_constructor(Container content) { return content.getFancyStuffs().stream().flatMap(item -> { ArrayList objects = new ArrayList<>(item.getElements()); objects.add(item.getElement()); return objects.stream(); }).collect(Collectors.toList()); }
Решение 4
Наконец, этот, где я уже рассчитываю размер окончательного списка, указывая количество элементов через конструктор. Это вообще предотвратит изменение размера списка массивов, потому что размер всегда будет соответствовать.
@Benchmark public Listwith_new_arraylist_constructor_size(Container content) { return content.getFancyStuffs().stream().flatMap(item -> { ArrayList objects = new ArrayList<>(item.getElements().size() + 1); objects.add(item.getElement()); objects.addAll(item.getElements()); return objects.stream(); }).collect(Collectors.toList()); }
Измерение
Измерение было выполнено на Машине Intel Xeon с частотой 3,50 ГГц с CentOS Linux версии 7.6.1810 (ядро) . Полный исходный код проекта можно найти Полный исходный код проекта можно найти
Базовое измерение
Поэтому я начал очень просто только с первых двух решений (1+2):
Ниже приводится лишь выдержка из вышесказанного Подробный результат измерения ((Я удалил префикс Объединение эталонного потока
из всех результатов здесь, в посте).
Benchmark Mode Cnt Score Error Units with_new_arraylist avgt 15 22.350 ± 1.415 us/op with_stream_concat avgt 15 15.716 ± 2.561 us/op
Поэтому, если вы посмотрите на результаты выше, вы уже можете увидеть, что для небольшого количества элементов (49 getElements, 50 экземпляров FanceStuff) один с stream_concat
быстрее. Hm..is это действительно так? Не ошибка измерения или совпадение?
Параметризованное Измерение
Чтобы уменьшить вероятность споткнуться о совпадение или ошибку измерения, я изменил код, который теперь принимает параметр, и увеличил количество элементов. Я придерживаюсь двух решений (1+2).
getElements()
всегда приводит к 49 элементам, в то время как количество Необычных элементов
варьируется (см. количество
). Следующий результат показывает, что версия с stream_concat
всегда быстрее.
Benchmark (count) Mode Cnt Score Error Units with_new_arraylist 50 avgt 15 21.759 ± 0.797 us/op with_new_arraylist 100 avgt 15 43.309 ± 1.449 us/op with_new_arraylist 1000 avgt 15 498.693 ± 103.550 us/op with_new_arraylist 2000 avgt 15 988.483 ± 80.574 us/op with_new_arraylist 5000 avgt 15 3379.189 ± 376.885 us/op with_stream_concat 50 avgt 15 17.695 ± 3.601 us/op with_stream_concat 100 avgt 15 38.559 ± 13.014 us/op with_stream_concat 1000 avgt 15 458.131 ± 95.578 us/op with_stream_concat 2000 avgt 15 815.142 ± 183.491 us/op with_stream_concat 5000 avgt 15 2682.883 ± 287.596 us/op
Интересно, что это относится не только к большему числу элементов. Это также относится к небольшому числу элементов корпуса.
Запуск всех решений
Так что, наконец, я запустил все решения (1+2+3+4 ) с разными числами (количество, количество элементов) с разными комбинациями. Я честно должен признать, что недооценил время, необходимое для завершения этого теста (оно заняло 13 часов+).
Я просто взял несколько примеров измеренного времени здесь:
Benchmark (count) (elementCount) Mode Cnt Score Error Units with_new_arraylist 10 1000 avgt 15 77,358 ± 2,957 us/op with_new_arraylist_constructor 10 1000 avgt 15 76,431 ± 1,544 us/op with_new_arraylist_constructor_size 10 1000 avgt 15 73,156 ± 0,118 us/op with_stream_concat 10 1000 avgt 15 68,461 ± 0,048 us/op .... with_new_arraylist 1000 1000 avgt 15 12864,161 ± 164,193 us/op with_new_arraylist_constructor 1000 1000 avgt 15 12703,437 ± 346,234 us/op with_new_arraylist_constructor_size 1000 1000 avgt 15 12401,783 ± 387,433 us/op with_stream_concat 1000 1000 avgt 15 11613,553 ± 632,684 us/op
Еще одна пробежка
Поэтому я также запустил решение со всеми возможными вариантами im JMH, которое заняло очень много времени (1 день + 15 часов+).
Поэтому я приведу здесь несколько примеров измеренного времени:
Benchmark (count) (elementCount) Mode Cnt Score Error Units with_new_arraylist 1000 500 ss 3 22169330,000 ± 25379203,013 ns/op with_new_arraylist_constructor 1000 500 ss 3 6425606,000 ± 4699006,445 ns/op with_new_arraylist_constructor_size 1000 500 ss 3 22911512,667 ± 13112575,975 ns/op with_stream_concat 1000 500 ss 3 5328312,333 ± 4162857,199 ns/op ... with_new_arraylist 1000 1000 ss 3 13283635,000 ± 7645310,577 ns/op with_new_arraylist_constructor 1000 1000 ss 3 35622844,333 ± 49138969,434 ns/op with_new_arraylist_constructor_size 1000 1000 ss 3 14122526,333 ± 4304061,268 ns/op with_stream_concat 1000 1000 ss 3 13405022,333 ± 17950218,966 ns/op
Итак, наконец, возникает вопрос: что означают эти цифры?
цитата из JMH вывода:
помнить: Приведенные ниже цифры – это просто данные. Чтобы получить многоразовую информацию, вам нужно проследить, почему цифры такие, какие они есть. Используйте профилировщики (см. -доказательство, -доказательство), проводите факторные эксперименты, выполняйте базовые и отрицательные тесты, обеспечивающие экспериментальный контроль, убедитесь, что среда бенчмаркинга безопасна на уровне JVM/OS/HW, запрашивайте отзывы у экспертов в области. Не думайте, что цифры говорят вам то, что вы хотите от них услышать.
Оригинал: “https://dev.to/khmarbaise/stream-concat-vs-new-arraylist-performance-within-a-stream-4ial”