Загадочный результат микробенчмарка для stream API на Java 12 по сравнению с Java 8 с -gc true

#java #java-stream #benchmarking #jmh #java-12

#java #java-stream #сравнительный анализ #jmh #java-12

Вопрос:

В рамках моего исследования разницы между использованием сложного фильтра или нескольких фильтров в потоках я заметил, что производительность на Java 12 намного медленнее, чем на Java 8.

Есть ли какое-либо объяснение этим странным результатам? Я что-то пропустил здесь?

Конфигурация:

  • java 8

    • Среда выполнения OpenJDK (сборка 1.8.0_181-8u181-b13-2 ~ deb9u1-b13)
    • 64-разрядная серверная виртуальная машина OpenJDK (сборка 25.181-b13, смешанный режим)
  • java 12

    • Среда выполнения OpenJDK (сборка 12 33)
    • 64-разрядная серверная виртуальная машина OpenJDK (сборка 12 33, смешанный режим, общий доступ)
  • Параметры виртуальной машины: -XX: UseG1GC -server -Xmx1024m -Xms1024m

  • Процессор: 8 ядер

Результаты пропускной способности JMH:

  • Прогрев: 10 итераций, по 1 сек каждая
  • Измерение: 10 итераций, по 1 сек каждая
  • Потоки: 1 поток, будет синхронизировать итерации
  • Единицы измерения: ops / s

Сравнительные таблицы

Код

Поток сложный фильтр

 public void complexFilter(ExecutionPlan plan, Blackhole blackhole) {
        long count = plan.getDoubles()
                .stream()
                .filter(d -> d < Math.PI
                        amp;amp; d > Math.E
                        amp;amp; d != 3
                        amp;amp; d != 2)
                .count();

        blackhole.consume(count);
    }
  

Поток несколько фильтров

 public void multipleFilters(ExecutionPlan plan, Blackhole blackhole) {
        long count = plan.getDoubles()
                .stream()
                .filter(d -> d > Math.PI)
                .filter(d -> d < Math.E)
                .filter(d -> d != 3)
                .filter(d -> d != 2)
                .count();

        blackhole.consume(count);
    }
  

Параллельный поток сложный фильтр

 public void complexFilterParallel(ExecutionPlan plan, Blackhole blackhole) {
        long count = plan.getDoubles()
                .stream()
                .parallel()
                .filter(d -> d < Math.PI
                        amp;amp; d > Math.E
                        amp;amp; d != 3
                        amp;amp; d != 2)
                .count();

        blackhole.consume(count);
    }
  

Параллельный поток несколько фильтров

 public void multipleFiltersParallel(ExecutionPlan plan, Blackhole blackhole) {
        long count = plan.getDoubles()
                .stream()
                .parallel()
                .filter(d -> d > Math.PI)
                .filter(d -> d < Math.E)
                .filter(d -> d != 3)
                .filter(d -> d != 2)
                .count();

        blackhole.consume(count);
    }

  

Старомодная итерация Java

 public void oldFashionFilters(ExecutionPlan plan, Blackhole blackhole) {
        long count = 0;
        for (int i = 0; i < plan.getDoubles().size(); i  ) {
            if (plan.getDoubles().get(i) > Math.PI
                    amp;amp; plan.getDoubles().get(i) > Math.E
                    amp;amp; plan.getDoubles().get(i) != 3
                    amp;amp; plan.getDoubles().get(i) != 2) {
                count = count   1;
            }
        }

        blackhole.consume(count);
    }

  

Вы можете попробовать сами, выполнив команду docker:

Для Java 8:

запуск docker -это volkodav / java-filter-benchmark: java8

Для Java 12:

запуск docker -это volkodav / java-filter-benchmark: java12

Исходный код:

https://github.com/volkodavs/javafilters-benchmarks

Комментарии:

1. Что означают эти цифры?

2. Довольно уверен, что -gc true в вашей конфигурации это портит jdk12. Принудительное заполнение GC перед каждой итерацией, скорее всего, приведет к отказу от эвристики GC. Почему у вас есть такая опция для начала?

3. Кроме того, почему @Setup(Level.Invocation) ? Похоже, ваша рабочая нагрузка хочет собрать все подводные камни сразу 🙂

4. Ответ может быть слишком сложным, чтобы соответствовать комментарию. Разница кажется реальной, и в случае jdk12 есть странные встроенные странности, как можно видеть с помощью -prof perfasm.

5. Теперь я думаю, что существует странное взаимодействие между полным GC и параллельными компиляциями. -gc true не рекомендуется по многим причинам, это может быть что-то новое. Все еще копаюсь…

Ответ №1:

Спасибо всем за помощь и особенно @Aleksey Shipilev!

После внесения изменений в тест JMH результаты выглядят более реалистично (?)

Изменения:

  1. Измените метод установки, который будет выполняться до / после каждой итерации теста.

    @Setup(Level.Invocation) -> @Setup(Level.Iteration)

  2. Остановите JMH, заставляющий выполнять GC между итерациями. Принудительное заполнение GC перед каждой итерацией, скорее всего, приведет к отказу от эвристики GC. (c) Алексей Шипилев

    -gc true -> -gc false

Примечание: по умолчанию gc false.

Сравнительные таблицы

Основываясь на новых тестах производительности, в Java 12 нет снижения производительности по сравнению с Java 8.

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

таблица результатов

Необработанные результаты

Java 8

 # Run complete. Total time: 04:36:29

Benchmark                                (arraySize)   Mode  Cnt         Score         Error  Units
FilterBenchmark.complexFilter                     10  thrpt   50   5947577.648 ±  257535.736  ops/s
FilterBenchmark.complexFilter                    100  thrpt   50   3131081.555 ±   72868.963  ops/s
FilterBenchmark.complexFilter                   1000  thrpt   50    489666.688 ±    6539.466  ops/s
FilterBenchmark.complexFilter                  10000  thrpt   50     17297.424 ±      93.890  ops/s
FilterBenchmark.complexFilter                 100000  thrpt   50      1398.702 ±      72.820  ops/s
FilterBenchmark.complexFilter                1000000  thrpt   50        81.309 ±       0.547  ops/s
FilterBenchmark.complexFilterParallel             10  thrpt   50     24515.743 ±     450.363  ops/s
FilterBenchmark.complexFilterParallel            100  thrpt   50     25584.773 ±     290.249  ops/s
FilterBenchmark.complexFilterParallel           1000  thrpt   50     24313.066 ±     425.817  ops/s
FilterBenchmark.complexFilterParallel          10000  thrpt   50     11909.085 ±      51.534  ops/s
FilterBenchmark.complexFilterParallel         100000  thrpt   50      3260.864 ±     522.565  ops/s
FilterBenchmark.complexFilterParallel        1000000  thrpt   50       406.297 ±      96.590  ops/s
FilterBenchmark.multipleFilters                   10  thrpt   50   3785766.911 ±   27971.998  ops/s
FilterBenchmark.multipleFilters                  100  thrpt   50   1806210.041 ±   11578.529  ops/s
FilterBenchmark.multipleFilters                 1000  thrpt   50    211435.445 ±   28585.969  ops/s
FilterBenchmark.multipleFilters                10000  thrpt   50     12614.670 ±     370.086  ops/s
FilterBenchmark.multipleFilters               100000  thrpt   50      1228.127 ±      21.208  ops/s
FilterBenchmark.multipleFilters              1000000  thrpt   50        99.149 ±       1.370  ops/s
FilterBenchmark.multipleFiltersParallel           10  thrpt   50     23896.812 ±     255.117  ops/s
FilterBenchmark.multipleFiltersParallel          100  thrpt   50     25314.613 ±     169.724  ops/s
FilterBenchmark.multipleFiltersParallel         1000  thrpt   50     23113.388 ±     305.605  ops/s
FilterBenchmark.multipleFiltersParallel        10000  thrpt   50     12676.057 ±     119.555  ops/s
FilterBenchmark.multipleFiltersParallel       100000  thrpt   50      3373.367 ±     211.108  ops/s
FilterBenchmark.multipleFiltersParallel      1000000  thrpt   50       477.870 ±      70.878  ops/s
FilterBenchmark.oldFashionFilters                 10  thrpt   50  45874144.758 ± 2210325.177  ops/s
FilterBenchmark.oldFashionFilters                100  thrpt   50   4902625.828 ±   60397.844  ops/s
FilterBenchmark.oldFashionFilters               1000  thrpt   50    662102.438 ±    5038.465  ops/s
FilterBenchmark.oldFashionFilters              10000  thrpt   50     29390.911 ±     257.311  ops/s
FilterBenchmark.oldFashionFilters             100000  thrpt   50      1999.032 ±       6.829  ops/s
FilterBenchmark.oldFashionFilters            1000000  thrpt   50       200.564 ±       1.695  ops/s
  

Java 12

 # Run complete. Total time: 04:36:20
    
Benchmark                                (arraySize)   Mode  Cnt         Score         Error  Units
FilterBenchmark.complexFilter                     10  thrpt   50  10338525.553 ? 1677693.433  ops/s
FilterBenchmark.complexFilter                    100  thrpt   50   4381301.188 ?  287299.598  ops/s
FilterBenchmark.complexFilter                   1000  thrpt   50    607572.430 ?    9367.026  ops/s
FilterBenchmark.complexFilter                  10000  thrpt   50     30643.286 ?     472.033  ops/s
FilterBenchmark.complexFilter                 100000  thrpt   50      1450.341 ?       3.730  ops/s
FilterBenchmark.complexFilter                1000000  thrpt   50       138.996 ?       2.052  ops/s
FilterBenchmark.complexFilterParallel             10  thrpt   50     21289.444 ?     183.245  ops/s
FilterBenchmark.complexFilterParallel            100  thrpt   50     20105.239 ?     124.759  ops/s
FilterBenchmark.complexFilterParallel           1000  thrpt   50     19418.830 ?     141.664  ops/s
FilterBenchmark.complexFilterParallel          10000  thrpt   50     13874.585 ?     104.418  ops/s
FilterBenchmark.complexFilterParallel         100000  thrpt   50      5334.947 ?      25.452  ops/s
FilterBenchmark.complexFilterParallel        1000000  thrpt   50       781.046 ?       9.687  ops/s
FilterBenchmark.multipleFilters                   10  thrpt   50   5460308.048 ?  478157.935  ops/s
FilterBenchmark.multipleFilters                  100  thrpt   50   2227583.836 ?  113078.932  ops/s
FilterBenchmark.multipleFilters                 1000  thrpt   50    287157.190 ?    1114.346  ops/s
FilterBenchmark.multipleFilters                10000  thrpt   50     16268.016 ?     704.735  ops/s
FilterBenchmark.multipleFilters               100000  thrpt   50      1531.516 ?       2.729  ops/s
FilterBenchmark.multipleFilters              1000000  thrpt   50       123.881 ?       1.525  ops/s
FilterBenchmark.multipleFiltersParallel           10  thrpt   50     20403.993 ?     147.247  ops/s
FilterBenchmark.multipleFiltersParallel          100  thrpt   50     19426.222 ?      96.979  ops/s
FilterBenchmark.multipleFiltersParallel         1000  thrpt   50     17692.433 ?      67.606  ops/s
FilterBenchmark.multipleFiltersParallel        10000  thrpt   50     12108.482 ?      34.500  ops/s
FilterBenchmark.multipleFiltersParallel       100000  thrpt   50      3782.756 ?      22.044  ops/s
FilterBenchmark.multipleFiltersParallel      1000000  thrpt   50       589.972 ?      71.448  ops/s
FilterBenchmark.oldFashionFilters                 10  thrpt   50  41024334.062 ? 1374663.440  ops/s
FilterBenchmark.oldFashionFilters                100  thrpt   50   6011852.027 ?  246202.642  ops/s
FilterBenchmark.oldFashionFilters               1000  thrpt   50    553243.594 ?    2217.912  ops/s
FilterBenchmark.oldFashionFilters              10000  thrpt   50     29188.753 ?     580.958  ops/s
FilterBenchmark.oldFashionFilters             100000  thrpt   50      2061.738 ?       8.456  ops/s
FilterBenchmark.oldFashionFilters            1000000  thrpt   50       196.105 ?       3.203  ops/s
  

Комментарии:

1. вызов -> Итерация был очевиден, gc — нет; ИМО, это не отвечает на вопрос. Я надеюсь, что Алексей представит свои результаты

2. Да, я согласен с вами, @Eugene, я уже попросил Алексея объяснить в комментариях. Я надеюсь, что он найдет время, чтобы предоставить больше информации об этом

3. Можете ли вы объяснить, что в этом очевидного? Я имею в виду, что это все еще регрессия? Поскольку это требует меньшего прогрева, означает ли это, что для 12 требуется больше времени? (Но тогда он все еще должен быть разогрет?)