Рубрики
Без рубрики

Поток.конкат против новая производительность списка массивов в потоке

Во время обзора кода я предложил улучшить код, связанный с потоками JDK8+. Исходный код выглядит так… С тегами java, jdk8, программирование, производительность.

Во время обзора кода я предложил улучшить код, связанный с потоками JDK8+. Исходный код выглядел очень похоже, как показано ниже:

List result = 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) .

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

List result = 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 List with_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 List with_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 List with_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 List with_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”