p_i*_*p_i 22 java arrays simd java-stream 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);
}
}
Run Code Online (Sandbox Code Playgroud)
但是,将 classP与结合使用IntStream,该方法test可能(错误地)返回true。上述main方法中的代码产生一些正整数,如716208. 每次执行后结果都会改变。
出现这种意外行为是因为在执行过程中可以将int数组state[]设置为零。如果一个测试代码,比如
if (seed == 0xf_fff0){
System.out.println(Arrays.toString(state));
}
Run Code Online (Sandbox Code Playgroud)
被插入到方法的尾部test,那么程序会输出像[1048560, 1048560, 1048560, 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].
问题:为什么可以将 int 数组state[]设置为零?
我已经知道如何避免这种行为:只需替换int[]为ArrayList.
我检查了:
dre*_*ash 23
人们可以用一个更简单的例子来重现这个问题,即:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
该问题是由JVM允许循环矢量化 (SIMD)的优化标志引起的(即,-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];
Run Code Online (Sandbox Code Playgroud)
进入:
System.arraycopy(state, 0, state, 1, SIZE - 1);
Run Code Online (Sandbox Code Playgroud)
例如:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
输出:
[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]
Run Code Online (Sandbox Code Playgroud)
新更新: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];
Run Code Online (Sandbox Code Playgroud)
(我提取了一个被调用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)
Run Code Online (Sandbox Code Playgroud)
这在我看来就像一个循环unrolling,另一方面,该方法java.util.stream.Streams$RangeIntSpliterator::forEachRemaining仅出现在带有标志的版本的程序集中。
| 归档时间: |
|
| 查看次数: |
572 次 |
| 最近记录: |