是否有任何 JVM 的 JIT 编译器生成使用矢量化浮点指令的代码?

2022-08-31 10:18:37

假设我的Java程序的瓶颈实际上是一些紧密的循环来计算一堆矢量点积。是的,我已经分析过了,是的,这是瓶颈,是的,它很重要,是的,这就是算法的方式,是的,我已经运行了Proguard来优化字节码,等等。

从本质上讲,这项工作是点产品。例如,我有两个,我需要计算成对乘积的总和。我知道处理器指令集的存在是为了快速和批量地执行这些类型的操作,如SSE或MMX。float[50]

是的,我可以通过在JNI中编写一些本机代码来访问这些代码。事实证明,JNI调用非常昂贵。

我知道你不能保证JIT会编译或不编译什么。有没有人听说过使用这些指令的JIT生成代码?如果是这样,Java代码有什么可以帮助它以这种方式编译吗?

可能是“不”;值得一问。


答案 1

所以,基本上,你希望你的代码运行得更快。JNI就是答案。我知道你说这对你不起作用,但让我告诉你你错了。

这里是:Dot.java

import java.nio.FloatBuffer;
import org.bytedeco.javacpp.*;
import org.bytedeco.javacpp.annotation.*;

@Platform(include = "Dot.h", compiler = "fastfpu")
public class Dot {
    static { Loader.load(); }

    static float[] a = new float[50], b = new float[50];
    static float dot() {
        float sum = 0;
        for (int i = 0; i < 50; i++) {
            sum += a[i]*b[i];
        }
        return sum;
    }
    static native @MemberGetter FloatPointer ac();
    static native @MemberGetter FloatPointer bc();
    static native @NoException float dotc();

    public static void main(String[] args) {
        FloatBuffer ab = ac().capacity(50).asBuffer();
        FloatBuffer bb = bc().capacity(50).asBuffer();

        for (int i = 0; i < 10000000; i++) {
            a[i%50] = b[i%50] = dot();
            float sum = dotc();
            ab.put(i%50, sum);
            bb.put(i%50, sum);
        }
        long t1 = System.nanoTime();
        for (int i = 0; i < 10000000; i++) {
            a[i%50] = b[i%50] = dot();
        }
        long t2 = System.nanoTime();
        for (int i = 0; i < 10000000; i++) {
            float sum = dotc();
            ab.put(i%50, sum);
            bb.put(i%50, sum);
        }
        long t3 = System.nanoTime();
        System.out.println("dot(): " + (t2 - t1)/10000000 + " ns");
        System.out.println("dotc(): "  + (t3 - t2)/10000000 + " ns");
    }
}

和:Dot.h

float ac[50], bc[50];

inline float dotc() {
    float sum = 0;
    for (int i = 0; i < 50; i++) {
        sum += ac[i]*bc[i];
    }
    return sum;
}

我们可以使用以下命令使用JavaCPP编译并运行它:

$ java -jar javacpp.jar Dot.java -exec

使用Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz,Fedora 30,GCC 9.1.1和OpenJDK 8或11,我得到这样的输出:

dot(): 39 ns
dotc(): 16 ns

或者大约快2.4倍。我们需要使用直接NIO缓冲区而不是数组,但HotSpot可以像数组一样快速地访问直接NIO缓冲区。另一方面,在这种情况下,手动展开循环并不能提供可衡量的性能提升。


答案 2

为了解决其他人在这里表达的一些怀疑态度,我建议任何想要向自己或其他人证明的人都使用以下方法:

  • 创建 JMH 项目
  • 写一小段可矢量化的数学。
  • 在 -XX:-UseSuperWord 和 -XX:+UseSuperWord(默认)之间运行他们的基准测试翻转
  • 如果未观察到性能差异,则您的代码可能未进行矢量化
  • 为确保安全,请运行基准测试,使其打印出程序集。在linux上,您可以享受perfasm profiler('-prof perfasm')查看并查看您期望的指令是否生成。

例:

@Benchmark
@CompilerControl(CompilerControl.Mode.DONT_INLINE) //makes looking at assembly easier
public void inc() {
    for (int i=0;i<a.length;i++)
        a[i]++;// a is an int[], I benchmarked with size 32K
}

带和不带标志的结果(在最近的Haswell笔记本电脑上,Oracle JDK 8u60):-XX:+UseSuperWord:475.073 ± 44.579 ns/op(每个op的纳秒数)-XX:-UseSuperWord:3376.364 ± 233.211 ns/op

热循环的程序集需要格式化并粘贴在这里,但这里有一个片段(hsdis.so 无法格式化一些AVX2矢量指令,所以我用-XX:UseAVX=1运行):-XX:+UseSuperWord(带有'-prof perfasm:intelSyntax=true')

  9.15%   10.90%  │││ │↗    0x00007fc09d1ece60: vmovdqu xmm1,XMMWORD PTR [r10+r9*4+0x18]
 10.63%    9.78%  │││ ││    0x00007fc09d1ece67: vpaddd xmm1,xmm1,xmm0
 12.47%   12.67%  │││ ││    0x00007fc09d1ece6b: movsxd r11,r9d
  8.54%    7.82%  │││ ││    0x00007fc09d1ece6e: vmovdqu xmm2,XMMWORD PTR [r10+r11*4+0x28]
                  │││ ││                                                  ;*iaload
                  │││ ││                                                  ; - psy.lob.saw.VectorMath::inc@17 (line 45)
 10.68%   10.36%  │││ ││    0x00007fc09d1ece75: vmovdqu XMMWORD PTR [r10+r9*4+0x18],xmm1
 10.65%   10.44%  │││ ││    0x00007fc09d1ece7c: vpaddd xmm1,xmm2,xmm0
 10.11%   11.94%  │││ ││    0x00007fc09d1ece80: vmovdqu XMMWORD PTR [r10+r11*4+0x28],xmm1
                  │││ ││                                                  ;*iastore
                  │││ ││                                                  ; - psy.lob.saw.VectorMath::inc@20 (line 45)
 11.19%   12.65%  │││ ││    0x00007fc09d1ece87: add    r9d,0x8            ;*iinc
                  │││ ││                                                  ; - psy.lob.saw.VectorMath::inc@21 (line 44)
  8.38%    9.50%  │││ ││    0x00007fc09d1ece8b: cmp    r9d,ecx
                  │││ │╰    0x00007fc09d1ece8e: jl     0x00007fc09d1ece60  ;*if_icmpge

有乐趣冲进城堡!