【问题标题】:Java 9: AES-GCM performanceJava 9:AES-GCM 性能
【发布时间】:2018-07-31 23:56:32
【问题描述】:

我运行了一个简单的测试,通过在循环中加密字节缓冲区来测量 Java 9 中的AES-GCM 性能。结果有些混乱。本机(硬件)加速似乎有效 - 但并非总是如此。更具体地说,

  1. 在循环中加密 1MB 缓冲区时,前约 50 秒的速度为约 60 MB/秒。然后它跳到 1100 MB/秒,并保持在那里。 JVM 是否决定在 50 秒(或 3GB 数据)后激活硬件加速?可以配置吗? 我在哪里可以了解新的 AES-GCM 实施 (besides here)。
  2. 加密 100MB 缓冲区时,硬件加速根本不会启动。速度为 60 MB/秒。

我的测试代码如下所示:

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 年 2 月更新:已修改 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

2019 年 7 月 16 日更新:新发布的 Java 版本(Java 11.0.4)修复了这个问题。

【问题讨论】:

  • 我认为你应该阅读How to write a correct micro-benchmark in Java 并应用那里描述的技术。他们可能会对您正在测量的内容有更好的了解。例如,您应该在基准测试中包含一个热身阶段。您看到的情况可能是因为 JIT 编译器会在 50 秒后启动并优化您的代码。
  • 似乎调用次数对优化触发器很重要,当然,考虑到相同的时间,处理大量小缓冲区意味着比处理几个大缓冲区更多的调用。注意通过重复调用update 处理大缓冲区的可能性,最后调用doFinal 处理最后一个块......
  • @Lii 你说得对,预热很可能会有所帮助,但这不是微基准,它需要相当长的时间并且应该会更好。
  • @Eugene 这不是关于采用不同的分支。我尝试了不同的缓冲区大小和不同的缓冲区大小。您可以在一秒钟内预热代码,方法是用一个很小的缓冲区经常执行它以获得优化,然后用一个巨大的缓冲区调用相同的代码,仍然可以从已经应用的优化中受益。这表明重要的仅仅是调用的数量。当您重构代码以始终通过重复的update 操作和doFinal 处理缓冲区的一小部分时,总缓冲区大小变得无关紧要……
  • @gg123 恕我直言,这应该是一个错误报告。大块的加密应该自动拆分,解密应该找到一些解决方案。

标签: java performance encryption java-11 aes-gcm


【解决方案1】:

感谢@Holger 指出正确的方向。在cipher.doFinal 前面加上多个cipher.update 调用将几乎立即触发硬件加速。

基于此参考,GCM Analysis,我在每次更新中使用 4KB 块。现在,1MB100MB 缓冲区都以 1100 MB/秒 的速度加密(几十毫秒后)。

解决办法是更换

byte[] cipherText = cipher.doFinal(input);

int clen = plen + GCM_TAG_LENGTH;
byte[] cipherText = new byte[clen];

int chunkLen = 4 * 1024;
int left = plen;
int inputOffset = 0;
int outputOffset = 0;

while (left > chunkLen) {
  int written = cipher.update(input, inputOffset, chunkLen, cipherText, outputOffset);
  inputOffset += chunkLen;
  outputOffset += written;
  left -= chunkLen;
}

cipher.doFinal(input, inputOffset, left, cipherText, outputOffset);

【讨论】:

    【解决方案2】:

    关于这个问题的一些更新。

    1. 3 月下旬发布的 Java 10 存在同样的问题,可以通过相同的解决方法绕过 - 仅用于数据加密。 p>

    2. 解决方法基本上不适用于数据解密 - 在 Java 9 和 Java 10 中。

    我已向 Java 平台提交了一份错误报告。它已被评估并发布为JDK-8201633

    【讨论】:

    【解决方案3】:

    此问题已在 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

    【讨论】:

      【解决方案4】:

      2019 年 7 月 16 日发布的 Java 版本 (Java 11.0.4) 修复了这个问题。

      【讨论】:

        猜你喜欢
        • 2016-05-12
        • 1970-01-01
        • 2019-12-14
        • 1970-01-01
        • 2019-03-11
        • 2022-01-26
        • 2019-07-06
        • 1970-01-01
        • 2017-10-21
        相关资源
        最近更新 更多