Java 9: ​​производительность AES-GCM

Я провел простой тест для измерения производительности AES-GCM в Java. 9, путем шифрования байтовых буферов в цикле. Результаты были несколько запутанными. Родное (аппаратное) ускорение вроде работает - но не всегда. В частности,

  1. При шифровании буферов размером 1 МБ в цикле скорость составляет ~60 МБ/с в течение первых ~50 секунд. Затем он подскакивает до 1100 МБ/с и остается там. Решит ли JVM активировать аппаратное ускорение через 50 секунд (или 3 ГБ данных)? его можно настроить? Где можно прочитать о новой реализации AES-GCM (помимо здесь).
  2. При шифровании 100-мегабайтных буферов аппаратное ускорение вообще не срабатывает. Скорость плоская 60 МБ/сек.

Мой тестовый код выглядит так:

int plen = 1024*1024;
byte[] input = new byte[plen];
for (int i=0; i < input.length; i++) { input[i] = (byte)i;}
byte[] nonce = new byte[12];
...
// Uses SunJCE provider
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
byte[] key_code = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
SecretKey key = new SecretKeySpec(key_code, "AES");
SecureRandom random = new SecureRandom();

long total = 0;
while (true) {
  random.nextBytes(nonce);
  GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, nonce);
  cipher.init(Cipher.ENCRYPT_MODE, key, spec);
  byte[] cipherText = cipher.doFinal(input);
  total += plen;
  // print delta_total/delta_time, once in a while
}

Обновление за февраль 2019 г. HotSpot был изменен для решения этой проблемы. Исправление применяется в Java 13, а также переносится на Java 11 и 12.

https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8201633, https://hg.openjdk.java.net/jdk/jdk/rev/f35a8aaabcb9

Обновление от 16 июля 2019 г. Недавно выпущенная версия Java (Java 11.0.4) устраняет эту проблему.


person gidon    schedule 21.02.2018    source источник
comment
забыл упомянуть - работает на коробке Ubuntu 16, один процессор (Intel Skylake) с 8 ядрами.   -  person gidon    schedule 21.02.2018
comment
Я думаю, вам следует прочитать о Как написать правильный микротест на Java и применить описанные там методы. С ними у вас, вероятно, будет лучшее представление о том, что вы измеряете. Например, вы должны включить фазу прогрева в свой тест. То, что вы видите, может быть связано с тем, что компилятор JIT срабатывает и оптимизирует ваш код через 50 секунд.   -  person Lii    schedule 21.02.2018
comment
дело не в методах бенчмаркинга. Знаете ли вы, что эта проблема связана с JIT, который срабатывает через 50 секунд / 3 ГБ? Если да, то это полезная информация для меня - это означает, что процесс, который начинается, шифрует, скажем, 2 ГБ данных и завершается, никогда не будет работать на скорости h/w. Кажется суровым. Может быть, это настраивается, или есть другое объяснение. Кроме того, почему он никогда не срабатывает с файлами размером 100 МБ? Любая информация об этом эффекте, вызванном JIT или нет, будет оценена по достоинству.   -  person gidon    schedule 21.02.2018
comment
Кажется, что количество вызовов имеет значение для триггера оптимизации, и, конечно же, обработка большого количества небольших буферов подразумевает большее количество вызовов, чем обработка нескольких больших буферов, учитывая то же количество времени. Помните о возможности обработки большого буфера, многократно вызывая update для небольшой его части и, наконец, вызывая doFinal для последнего фрагмента…   -  person Holger    schedule 21.02.2018
comment
@Lii Вы правы, разминка, скорее всего, поможет, но это не микротест, это занимает довольно много времени и должно работать лучше.   -  person maaartinus    schedule 21.02.2018
comment
@maaartinus Мне просто интересно, связано ли это со встроенными функциями здесь, вероятно, 50 секунд - это время, когда достигается минимальное количество требуемых вызовов метода, так что определенный метод интринсифицируется. Я также задаюсь вопросом, будут ли большие буферы означать переход к другой ветке, где невозможны встроенные функции, но я размышляю здесь   -  person Eugene    schedule 21.02.2018
comment
Ваш вопрос может быть не о методах сравнительного анализа, но без надлежащего сравнительного анализа мало что можно сделать. Так что кто-то все равно должен их написать (или, возможно, уже написал во время разработки этой самой фичи!).   -  person the8472    schedule 22.02.2018
comment
@Eugene, дело не в том, чтобы брать разные ветки. Я пробовал это с разными размерами буфера, а также с разными размерами буфера. Вы можете разогреть код за секунду, выполняя его достаточно часто с крошечным буфером, чтобы получить оптимизацию, а затем вызывать тот же код с огромным буфером, все еще получая выгоду от уже примененной оптимизации. Это указывает на то, что имеет значение только количество вызовов. Когда вы реорганизуете код, чтобы всегда обрабатывать одинаково маленькую часть буфера с помощью повторяющихся операций update, за которыми следует doFinal, общий размер буфера становится неактуальным...   -  person Holger    schedule 22.02.2018
comment
@Holger, тогда в этом мало смысла, это должно быть жестко закодировано где-то в коде AES (или флагом, о котором мы не знаем), все еще очень странно   -  person Eugene    schedule 22.02.2018
comment
@Holger Спасибо! Вот и все — разделение шифрования на несколько обновлений решает эту проблему. Грубо говоря, для разогрева кода требуется 10 000 операций. Я размещаю дополнительную информацию ниже.   -  person gidon    schedule 22.02.2018
comment
@Holger, так что тогда речь идет о разогреве, 10_000 (примерно) является пределом, когда какой-либо метод попадает в компилятор C2, когда определенный метод заменяется внутренним вызовом, где срабатывает аппаратное ускорение.   -  person Eugene    schedule 22.02.2018
comment
@Holger, Юджин: исправляю мои цифры выше - с дополнительными экспериментами похоже, что оптимизация начинается раньше, примерно после 600 операций (~ 40 миллисекунд с фрагментами по 4 КБ, ~ 160 миллисекунд с фрагментами по 16 КБ).   -  person gidon    schedule 22.02.2018
comment
@Holger Плохие новости. Это работает только для шифрования. Расшифровка разогревается только операциями doFinal, а не обновлениями. Пожалуйста, дайте мне знать, если вы видите другую картину в вашем окружении.   -  person gidon    schedule 22.02.2018
comment
@ gg123 ИМХО об этом следует сообщать как об ошибке. Шифрование огромных блоков должно автоматически разделяться, а для расшифровки должно быть найдено какое-то решение.   -  person maaartinus    schedule 23.02.2018
comment
@maaartinus Вы правы. Я ждал Java 10, чтобы увидеть, было ли это решено, но результаты в основном такие же (с некоторым обходным путем для расшифровки - сложным и ненадежным ..). Отправка ошибки.   -  person gidon    schedule 12.04.2018


Ответы (4)



Пара обновлений по этому вопросу.

  1. Java 10, выпущенная в конце марта, имеет ту же проблему, которую можно обойти тем же обходным путем — только для шифрования данных.

  2. Обходной путь в основном не работает для расшифровки данных — как в Java 9, так и в Java 10.

Я отправил отчет об ошибке на платформу Java. Он был оценен и опубликован как JDK-8201633.

person gidon    schedule 12.04.2018
comment
Команда Apache PARquet начала обсуждение списков OpenJDK: mail. openjdk.java.net/pipermail/security-dev/2018-ноябрь/ - person eckes; 15.11.2018

Эта проблема исправлена ​​в Java 13. Исправление также переносится на Java 11 и 12.

https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8201633, https://hg.openjdk.java.net/jdk/jdk/rev/f35a8aaabcb9

person gidon    schedule 14.02.2019

Версия Java, выпущенная 16 июля 2019 г. (Java 11.0.4), устраняет эту проблему.

person gidon    schedule 18.07.2019