使用intstream会导致int[]的元素在实现的方法中被不适当地设置为零(jvm bug,java11)

ozxc1zmp  于 2021-06-30  发布在  Java
关注(0)|答案(1)|浏览(270)

在班上 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);
    }
}

组合类 PIntStream 然而,方法 test 可以返回 true . 中的代码 main 上面的方法会产生一些正整数,如 716208 . 每次执行后结果都会改变。
由于int数组 state[] 可以在执行期间设置为零。如果一个测试代码,比如

if (seed == 0xf_fff0){
    System.out.println(Arrays.toString(state));
}

在方法的末尾插入 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 .
我的研究对象是:
windows 10+openjdk运行时环境(内部版本15.0.1+9-18)openjdk 64位服务器虚拟机(内部版本15.0.1+9-18,混合模式,共享)
debian 10+openjdk运行时环境(build 15.0.1+9-18)openjdk 64位服务器虚拟机(build 15.0.1+9-18,混合模式,共享)
debian 9+openjdk运行时环境采用openjdk(build 13.0.1+9)openjdk 64位服务器vm采用openjdk(build 13.0.1+9,混合模式,共享)

e37o9pze

e37o9pze1#

我们可以用一个更简单的例子来重现这个问题,即:

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);

    }
}

问题是由 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];

分为:

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
如果可以,请尝试从以下位置构建和测试最新的openjdk11uhttps://hg.openjdk.java.net/jdk-updates/jdk11u-dev/
从第一个链接,可以看到以下内容:
@bug 8251994@流的摘要测试矢量化$rangeintspliterator::foreachremaining@需要vm.compiler2.enabled&vm.compmode!““辛特”
@运行main compiler.vectorization.testforeachrem test1@run main compiler.vectorization.testforeachrem test2@run main compiler.vectorization.testforeachrem test3@run main compiler.vectorization.testforeachrem test4
从jira关于这个bug的故事的评论中,我们可以看到:
我找到了问题的原因。为了提高对循环进行矢量化的机会,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\u parallel()方法能够创建包,它们也会被filter\u packs()方法删除。此外,上述情况是矢量化的,没有起重负荷和平行 Package -我验证了它。那个代码现在是无用的,我将把它放在旗下不运行它。它需要更多的工作才能有用。我不愿意删除代码,因为可能在未来我们将有时间投资于它。
这也许可以解释为什么我在比较有标记和没有标记的版本的程序集时 -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 只出现在带有标志的版本的程序集中。

相关问题