Почему повторное распределение памяти наблюдается медленнее при использовании Epsilon против G1? - PullRequest
4 голосов
/ 24 сентября 2019

Мне было любопытно измерить время, потраченное на выделение памяти в 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
  • Выделение + синхронизация + выход, Epsilon: 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 для каждого набора чисел).

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

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

enter image description here

1 Ответ

2 голосов
/ 25 сентября 2019

@ В приведенном выше комментарии Хольгера объясняется, чего мне не хватало в исходном тесте - получение новой памяти из ОС обходится дороже, чем переработка памяти в JVM.В комментарии @ the8472 указывалось, что код приложения не сохраняет ссылок ни на один из выделенных массивов, поэтому тест не проверял то, что я хотел.Изменяя тест, чтобы сохранить ссылку на каждый новый массив, результаты теперь показывают, что Epsilon превосходит G1.

Вот что я сделал в коде, чтобы сохранить ссылки.Определите это как переменную-член:

static ArrayList<byte[]> savedArrays = new ArrayList<>(1024);

, а затем добавляйте ее после каждого выделения:

savedArrays.add(array);

Эпсилон-выделения аналогичны ранее, что ожидается:

$ time java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC Scratch repeatedAllocations
real    0m0.587s
user    0m0.312s
sys     0m0.296s

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

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

G1 теперь намного медленнее, чем раньше, и также медленнее, чем Epsilon:

$ time java -XX:+UseG1GC Scratch repeatedAllocations
real    0m0.884s
user    0m1.265s
sys     0m0.538s

$ time java -XX:+UseG1GC Scratch repeatedAllocations
real    0m0.884s
user    0m1.251s
sys     0m0.533s

$ time java -XX:+UseG1GC Scratch repeatedAllocations
real    0m0.864s
user    0m1.214s
sys     0m0.528s

Повторное выполнение времени на выделение с использованием repeatedAllocationsWithTimingAndOutput(), теперь средние значения соответствуют Epsilon быстрее.

average time (in nanos) for 1,024 consecutive 1MB array allocations
Epsilon 491,665
G1      883,981
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...