Почему повторное распределение памяти наблюдается медленнее при использовании Epsilon против G1?

Мне было любопытно измерить время, потраченное на выделение памяти в JDK 13 с использованием G1 и Epsilon. Результаты, которые я наблюдал, являются неожиданными, и мне интересно понять, что происходит. В конечном счете, я пытаюсь понять, как сделать использование Epsilon более производительным, чем G1 (или, если это невозможно, почему).

Я написал небольшой тест, который многократно выделяет память. В зависимости от ввода из командной строки: либо

  • создаст 1024 новых массива 1 МБ, либо
  • создаст 1024 новых массива 1 МБ, измерит время вокруг выделения и распечатает прошедшее время Это не измеряет только само распределение, и включает время, прошедшее для чего-либо еще, что происходит между двумя вызовами к System.nanoTime() - тем не менее, это, кажется, полезный сигнал для прослушивания.

Вот код:

public static void main(String[] args) {
    if (args[0].equals("repeatedAllocations")) {
        repeatedAllocations();
    } else if (args[0].equals("repeatedAllocationsWithTimingAndOutput")) {
        repeatedAllocationsWithTimingAndOutput();
    }
}

private static void repeatedAllocations() {
    for (int i = 0; i < 1024; i++) {
        byte[] array = new byte[1048576]; // allocate new 1MB array
    }
}

private static void repeatedAllocationsWithTimingAndOutput() {
    for (int i = 0; i < 1024; i++) {
        long start = System.nanoTime();
        byte[] array = new byte[1048576]; // allocate new 1MB array
        long end = System.nanoTime();
        System.out.println((end - start));
    }
}

Вот информация о версии для JDK, который я использую:

$ java -version
openjdk version "13-ea" 2019-09-17
OpenJDK Runtime Environment (build 13-ea+22)
OpenJDK 64-Bit Server VM (build 13-ea+22, mixed mode, sharing)

Вот различные способы запуска программа:

  • только выделение с использованием G1: $ time java -XX:+UseG1GC Scratch repeatedAllocations
  • только выделение, Epsilon: $ time java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC Scratch repeatedAllocations
  • выделение + синхронизация + выход с использованием G1: $ time java -XX:+UseG1GC Scratch repeatedAllocationsWithTimingAndOutput
  • выделение + синхронизация + выход, эпсилон: time java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC Scratch repeatedAllocationsWithTimingAndOutput

Вот некоторые временные параметры запуска G1 только с выделениями:

$ time java -XX:+UseG1GC Scratch repeatedAllocations
real    0m0.280s
user    0m0.404s
sys     0m0.081s

$ time java -XX:+UseG1GC Scratch repeatedAllocations
real    0m0.293s
user    0m0.415s
sys     0m0.080s

$ time java -XX:+UseG1GC Scratch repeatedAllocations
real    0m0.295s
user    0m0.422s
sys     0m0.080s

$ time java -XX:+UseG1GC Scratch repeatedAllocations
real    0m0.296s
user    0m0.422s
sys     0m0.079s

Вот некоторые временные параметры от запуска Epsilon только с распределениями:

$ time java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC Scratch repeatedAllocations
real    0m0.665s
user    0m0.314s
sys     0m0.373s

$ time java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC Scratch repeatedAllocations
real    0m0.652s
user    0m0.313s
sys     0m0.354s

$ time java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC Scratch repeatedAllocations
real    0m0.659s
user    0m0.314s
sys     0m0.362s

$ time java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC Scratch repeatedAllocations
real    0m0.665s
user    0m0.320s
sys     0m0.367s

С или без синхронизации + выход, G1 быстрее, чем Epsilon. В качестве дополнительного измерения, используя временные числа из repeatedAllocationsWithTimingAndOutput, среднее время распределения больше при использовании Epsilon. В частности, один из локальных прогонов показал, что G1GC в среднем составлял 227,218 нанос на распределение, тогда как Epsilon составлял в среднем 521,217 нанос (я записал выходные числа, вставил их в электронную таблицу и использовал функцию average для каждого набора чисел).

Я ожидал, что тесты Эпсилона будут заметно быстрее, однако на практике я вижу примерно в 2 раза медленнее. Максимальное время выделения с G1 определенно выше, но только с перерывами - большинство распределений G1 значительно медленнее, чем у Epsilon, почти на один порядок медленнее.

Вот график 1024 раз из repeatedAllocationsWithTimingAndOutput() с G1 и Эпсилоном. Темно-зеленый - для G1; светло-зеленый для Эпсилон; Ось Y - «нанос на распределение»; Меньшие линии сетки по оси Y каждые 250000 нанос. Это показывает, что время выделения Epsilon очень стабильно, каждый раз около 300-400 тыс. Нано. Это также показывает, что время G1 значительно быстрее в большинстве случаев, но также периодически - в 10 раз медленнее, чем у Epsilon. Я предполагаю, что это может быть связано с работой сборщика мусора, что было бы нормальным и нормальным, но также, кажется, сводит на нет идею, что G1 достаточно умен, чтобы знать, что ему не нужно выделять какую-либо новую память.

[+1113] [+1113]

16
задан kaan 25 September 2019 в 02:14
поделиться