可以用一个更简单的例子重现这个问题,即:
class Main {
private final static int SIZE = 33;
public static boolean test2(int seed) {
int[] state = new int[SIZE];
state[0] = seed;
for (int i = 1; i < SIZE; i++) {
state[i] = state[i - 1];
}
return seed != state[SIZE - 1];
}
public static void main(String[] args) {
long count = IntStream.range(0, 0x0010_0000).filter(Main::test2).count();
System.out.println(count);
}
}
该问题是由允许循环(即 )的矢量化 (SIMD) 的优化标志引起的。可能是由于对具有相交范围(即 )的同一数组应用矢量化而产生的。如果 (对于 的某些元素),则可能会重现类似的问题,优化循环:JVM
-XX:+AllowVectorizeOnDemand
state[i] = state[i - 1];
JVM
IntStream.range(0, 0x0010_0000)
for (int i = 1; i < SIZE; i++)
state[i] = state[i - 1];
到:
System.arraycopy(state, 0, state, 1, SIZE - 1);
例如:
class Main {
private final static int SIZE = 33;
public static boolean test2(int seed) {
int[] state = new int[SIZE];
state[0] = seed;
System.arraycopy(state, 0, state, 1, SIZE - 1);
if(seed == 100)
System.out.println(Arrays.toString(state));
return seed != state[SIZE - 1];
}
public static void main(String[] args) {
long count = IntStream.range(0, 0x0010_0000).filter(Main::test2).count();
System.out.println(count);
}
}
输出:
[100, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
新更新: 01/01/2021
我已向参与该标志的实现/集成的开发人员之一发送电子邮件,收到了以下回复:-XX:+AllowVectorizeOnDemandand
众所周知,AllowVectorizeOnDemand代码的一部分被破坏了。
有修复(它排除了执行不正确的矢量化的损坏代码),它被向后移植到jdk 11.0.11中:
https://hg.openjdk.java.net/jdk-updates/jdk11u-dev/rev/69dbdd271e04
如果可以,请尝试从 https://hg.openjdk.java.net/jdk-updates/jdk11u-dev/ 构建和测试最新的 OpenJDK11u
从第一个链接中,可以阅读以下内容:
@bug 8251994 @summary Streams$RangeIntSpliterator::forEachRemaining @requires vm.compiler2.enabled & vm.compMode 的测试矢量化 != “Xint”
@run main compiler.vectorization.TestForEachRem test1
@run main compiler.vectorization.TestForEachRem test2
@run main compiler.vectorization.TestForEachRem test3
@run main compiler.vectorization.TestForEachRem test4
从JIRA关于该错误的故事的评论中,人们可以读到:
我找到了问题的原因。为了提高对循环进行矢量化的机会,superword 尝试通过将其内存输入替换为相应的(相同的内存片)循环的内存 Phi 来将负载提升到循环的开头:http://hg.openjdk.java.net/jdk/jdk/file/8f73aeccb27c/src/hotspot/share/opto/superword.cpp#l471
最初,加载按同一内存片上的相应存储进行排序。但是当他们被吊起时,他们失去了这种命令 - 没有什么可以执行命令。在 test6 案例中,仅当矢量大小为 32 字节 (avx2) 但以 16 (avx=0 或 avx1) 或 64 (avx512) 字节向量无序时,才会在提升后保留排序(幸运的是?)。(...)
我有简单的修复(使用原始加载排序索引),但是查看导致问题的代码,我发现它是虚假的/不完整的 - 它无助于JDK-8076284更改列出的案例:
https://mail.openjdk.java.net/pipermail/hotspot-compiler-dev/2015-April/017645.html
使用展开和克隆信息进行矢量化是一个有趣的想法,但正如我所看到的,它并不完整。即使pack_parallel()方法能够创建包,它们也会被filter_packs()方法删除。此外,上述情况在没有提升负载的情况下进行了矢量化,并pack_parallel - 我验证了它。该代码现在毫无用处,我将把它放在标志下不运行它。它需要更多的工作才能有用。我不愿意删除代码,因为将来我们可能会有时间投入其中。
这也许可以解释为什么当我比较带有和不带标志的版本汇编时,我注意到带有标志的版本用于以下代码:-XX:+AllowVectorizeOnDemand
for (int i = 1; i < SIZE; i++)
state[i] = state[i - 1];
(我在调用的方法上提取,以便于在程序集中查找它),具有:hotstop
00000001162bacf5: mov %r8d,0x10(%rsi,%r10,4)
0x00000001162bacfa: mov %r8d,0x14(%rsi,%r10,4)
0x00000001162bacff: mov %r8d,0x18(%rsi,%r10,4)
0x00000001162bad04: mov %r8d,0x1c(%rsi,%r10,4)
0x00000001162bad09: mov %r8d,0x20(%rsi,%r10,4)
0x00000001162bad0e: mov %r8d,0x24(%rsi,%r10,4)
0x00000001162bad13: mov %r8d,0x28(%rsi,%r10,4)
0x00000001162bad18: mov %r8d,0x2c(%rsi,%r10,4) ;*iastore {reexecute=0 rethrow=0 return_oop=0}
; - AAAAAA.Main::hotstop@15 (line 21)
在我看来,这就像一个循环 ,从那边来看,该方法只出现在带有标志的版本的集合中。unrolling
java.util.stream.Streams$RangeIntSpliterator::forEachRemaining