#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
Исходный код:
Комментарии:
1. Что означают эти цифры?
2. Довольно уверен, что
-gc true
в вашей конфигурации это портит jdk12. Принудительное заполнение GC перед каждой итерацией, скорее всего, приведет к отказу от эвристики GC. Почему у вас есть такая опция для начала?3. Кроме того, почему
@Setup(Level.Invocation)
? Похоже, ваша рабочая нагрузка хочет собрать все подводные камни сразу 🙂4. Ответ может быть слишком сложным, чтобы соответствовать комментарию. Разница кажется реальной, и в случае jdk12 есть странные встроенные странности, как можно видеть с помощью -prof perfasm.
5. Теперь я думаю, что существует странное взаимодействие между полным GC и параллельными компиляциями.
-gc true
не рекомендуется по многим причинам, это может быть что-то новое. Все еще копаюсь…
Ответ №1:
Спасибо всем за помощь и особенно @Aleksey Shipilev!
После внесения изменений в тест JMH результаты выглядят более реалистично (?)
Изменения:
-
Измените метод установки, который будет выполняться до / после каждой итерации теста.
@Setup(Level.Invocation)
->@Setup(Level.Iteration)
-
Остановите 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 требуется больше времени? (Но тогда он все еще должен быть разогрет?)