如何解决IntStream 导致数组元素被错误地设置为 0JVM 错误,Java 11
在下面的类 P
中,方法 test
似乎返回相同的 false
:
import java.util.function.IntPredicate;
import java.util.stream.IntStream;
public class P implements IntPredicate {
private final static int SIZE = 33;
@Override
public boolean test(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(new P()).count();
System.out.println(count);
}
}
将类 P
与 IntStream
结合使用,方法 test
可以(错误地)返回 true
。
上面 main
方法中的代码会产生一些正整数,例如 716208
。
每次执行后结果都会改变。
发生这种意外行为是因为在执行过程中可以将 int
数组 state[]
设置为零。
如果一个测试代码,比如
if (seed == 0xf_fff0){
System.out.println(Arrays.toString(state));
}
被插入到方法test
的尾部,那么程序会输出类似[1048560,1048560,0]
的一行。
问题:为什么可以将 int 数组 state[]
设置为零?
我已经知道如何避免这种行为:只需将 int[]
替换为 ArrayList
。
我检查了:
- windows 10 + 和 debian 10+ 和 OpenJDK 运行时环境(构建 15.0.1+9-18)OpenJDK 64 位服务器 VM(构建 15.0.1+9-18,混合模式,共享)
- debian 9 + OpenJDK 运行时环境 AdoptOpenJDK(构建 13.0.1+9)OpenJDK 64 位服务器 VM AdoptOpenJDK(构建 13.0.1+9,混合模式,共享)
解决方法
我们可以用一个更简单的例子来重现这个问题,即:
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,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,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]
新更新:2021 年 1 月 1 日
我已向参与该标志的实施/集成的其中一位开发人员发送了一封电子邮件-XX:+AllowVectorizeOnDemandand
,收到以下回复:
已知部分 AllowVectorizeOnDemand 代码已损坏。
有修复(它排除了执行不正确的损坏代码 向量化),它被反向移植到 jdk 11.0.11:
https://hg.openjdk.java.net/jdk-updates/jdk11u-dev/rev/69dbdd271e04
如果可以,请尝试构建和测试最新的 OpenJDK11u https://hg.openjdk.java.net/jdk-updates/jdk11u-dev/
从第一个链接,您可以阅读以下内容:
@bug 8251994 @summary 测试 Streams$RangeIntSpliterator::forEachRemaining 的矢量化 @需要 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 story 评论中,可以阅读:
我找到了问题的原因。为了提高向量化的机会 循环,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,4)
0x00000001162bacff: mov %r8d,0x18(%rsi,4)
0x00000001162bad04: mov %r8d,0x1c(%rsi,4)
0x00000001162bad09: mov %r8d,0x20(%rsi,4)
0x00000001162bad0e: mov %r8d,0x24(%rsi,4)
0x00000001162bad13: mov %r8d,0x28(%rsi,4)
0x00000001162bad18: mov %r8d,0x2c(%rsi,4) ;*iastore {reexecute=0 rethrow=0 return_oop=0}
; - AAAAAA.Main::hotstop@15 (line 21)
在我看来,它像一个循环 unrolling
,另一方面,方法 java.util.stream.Streams$RangeIntSpliterator::forEachRemaining
只出现在带有标志的版本的程序集中。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。