Производительность конкатенации строк Java 11 против Java 8 - PullRequest
4 голосов
/ 28 марта 2019

Кто-нибудь знает, почему я получаю такую ​​разную производительность при выполнении этого кода на Java 8 и Java 11?

Без использования каких-либо флагов времени выполнения этот код работает значительно медленнее в Java 11 по сравнению с Java 8.

import java.util.Date;

public class PerformanceExperiment {
    public static volatile String s = "";

    public static void main(String[] args)
    {                         
        System.out.println("Starting performance test");
        String s1 = "STRING ONE";
        String s2 = "STRING TWO";
        long now1 = (new Date()).getTime();
        for (long i = 0; i < 1_000_000_00; i++)
        {
            s = "abc " + s1 + " def " + s2;
        }
        long now2 = (new Date()).getTime();
        System.out.println("initial block took " + (now2 - now1) + "ms");
        for (long i = 0; i < 4_000_000_00; i++)
        {
            s = "abc " + s1 + " def " + s2;
        }
        long now3 = (new Date()).getTime();
        System.out.println("Main block took " + (now3 - now2) + "ms");
    }
}

Я пробовал многочисленные флаги командной строки, но не смог найти ничего, что соответствовало бы производительности Java 8.

Я проверял это только в Windows, поэтому в других операционных системах он может работать иначе.

Ответы [ 3 ]

6 голосов
/ 28 марта 2019

Я изменил ваше приложение на

  1. Используйте System.nanoTime() вместо new Date() для большей точности (см. Этот ответ для получения дополнительной информации: https://stackoverflow.com/a/1776053/963076).
  2. Использовать профилировщик Netbeans.
  3. Цикл 10 итераций

Использование Netbeans 8.2 с JDK 8 v181:

Starting performance test 0
initial block took 3147ms
Main block took 9469ms
Starting performance test 1
initial block took 2398ms
Main block took 9601ms
Starting performance test 2
initial block took 2463ms
Main block took 9671ms
Starting performance test 3
initial block took 2464ms
Main block took 9565ms
Starting performance test 4
initial block took 2410ms
Main block took 9672ms
Starting performance test 5
initial block took 2418ms
Main block took 9598ms
Starting performance test 6
initial block took 2384ms
Main block took 9733ms
Starting performance test 7
initial block took 2402ms
Main block took 9610ms
Starting performance test 8
initial block took 2509ms
Main block took 11222ms
Starting performance test 9
initial block took 2455ms
Main block took 10661ms

И профилировщик показал эту телеметрию:

enter image description here

В Netbeans 10.0 с использованием JDK 11.0.2:

Starting performance test 0
initial block took 3760ms
Main block took 15056ms
Starting performance test 1
initial block took 3734ms
Main block took 14602ms
Starting performance test 2
initial block took 3615ms
Main block took 14762ms
Starting performance test 3
initial block took 3748ms
Main block took 14534ms
Starting performance test 4
initial block took 3628ms
Main block took 14759ms
Starting performance test 5
initial block took 3625ms
Main block took 14959ms
Starting performance test 6
initial block took 3987ms
Main block took 14967ms
Starting performance test 7
initial block took 3803ms
Main block took 14701ms
Starting performance test 8
initial block took 3599ms
Main block took 14762ms
Starting performance test 9
initial block took 3627ms
Main block took 14434ms

enter image description here

Мой вывод: JDK 11 делает гораздо больше работы, чтобы эффективно использовать память. Обратите внимание, что количество «выживших поколений» в сборщике мусора намного меньше (с помощью JDK11), а использование памяти также значительно меньше с точки зрения использования и изменчивости. Компромисс, кажется, в скорости, но разница в скорости меньше, чем разница в использовании памяти.

2 голосов
/ 01 апреля 2019

TL; DR: Нужны лучшие тесты, лучшая настройка с контролем различий между выпусками и т. Д. Большинство проблем сравнительного анализа будет тривиально решаться с использованием JMH. Похоже, что текущее поведение теста объясняется сомнительным подходом к тестированию и изменением GC по умолчанию.

Учтите это:

public class PerformanceExperiment {
    public static volatile String s = "";

    public static void main(String[] args) {
        for (int c = 0; c < 5; c++) {
            test();
        }
    }

    public static void test() {
        String s1 = "STRING ONE";
        String s2 = "STRING TWO";
        long time1 = System.currentTimeMillis();
        for (long i = 0; i < 4_000_000_00; i++) {
            s = "abc " + s1 + " def " + s2;
        }
        long time2 = System.currentTimeMillis();
        System.out.println("Main block took " + (time2 - time1) + "ms");
    }
}

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

Затем JIT-компиляция попадет в метод, и вы захотите повторно ввести метод, чтобы запустить оптимизированный код, в противном случае вы запускаете промежуточный код «замена на стеке» - вы можете сделать это с внешним итерация, которая вызывает test. И вдобавок ко всему, вы хотите ввести несколько раз, чтобы получить наиболее оптимизированную версию.

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

Итак, вот:

$ ~/Install/jdk8u191-rh/bin/javac PerformanceExperiment.java
$ ~/Install/jdk8u191-rh/bin/java -Xms2g -Xmx2g PerformanceExperiment
Main block took 10024ms
Main block took 9768ms
Main block took 7249ms
Main block took 7235ms
Main block took 7205ms

... а вот 11.0.2 с тем же байт-кодом:

$ ~/Install/jdk11.0.2/bin/java -Xms2g -Xmx2g PerformanceExperiment
Main block took 9775ms
Main block took 10825ms
Main block took 8635ms
Main block took 8616ms
Main block took 8622ms

... а вот 11.0.2 с соответствующим GC (9+ изменил значение по умолчанию на G1 с JEP 248 ):

$ ~/Install/jdk11.0.2/bin/java -Xms2g -Xmx2g -XX:+UseParallelGC PerformanceExperiment
Main block took 9281ms
Main block took 9129ms
Main block took 6725ms
Main block took 6688ms
Main block took 6684ms

Кроме того, на каждой крошечной итерации есть магазин volatile, который стоит довольно дорого и, вероятно, искажает эталонный тест.

Существует также взаимодействие с указанным конкататом строк (JEP 280) , локальными рукопожатиями (JEP 312) и другими исправлениями ВМ, но вы вероятно, увидит это только при компиляции прошлого target = 8, что выходит за рамки этого упражнения.

0 голосов
/ 01 апреля 2019

Теперь это подтверждено как ошибка в JDK:

https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8221733

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...