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

Sea*_*wen 91 java floating-point jit sse vectorization

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

这项工作基本上是点产品.因为,我有两个float[50],我需要计算成对产品的总和.我知道处理器指令集可以快速和批量地执行这些操作,如SSE或MMX.

是的我可以通过在JNI中编写一些本机代码来访问它们.JNI电话证明非常昂贵.

我知道你无法保证JIT编译或编译的内容.有没有人曾经听说过使用这些指令的JIT生成的代码?如果有的话,有什么关于Java代码可以帮助它以这种方式编译吗?

可能是"不"; 值得一提.

Sam*_*det 43

所以,基本上,您希望代码运行得更快.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");
    }
}
Run Code Online (Sandbox Code Playgroud)

并且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;
}
Run Code Online (Sandbox Code Playgroud)

我们可以使用命令行编译和运行JavaCPP:

$ java -jar javacpp.jar Dot.java -exec
Run Code Online (Sandbox Code Playgroud)

使用英特尔酷睿i7-3632QM CPU @ 2.20GHz,Fedora 20,GCC 4.8.3和OpenJDK 7或8,我得到这种输出:

dot(): 39 ns
dotc(): 16 ns
Run Code Online (Sandbox Code Playgroud)

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

  • 您使用过OpenJDK还是Oracle HotSpot?与流行的看法相反,它们并不相同. (3认同)
  • @Oliv GCC使用SSE对代码进行矢量化处理,是的,但是对于这么小的数据,不幸的是JNI调用开销太大。 (2认同)
  • 在装有 JDK 13 的 A6-7310 上,我得到:dot(): 69 ns / dotc(): 95 ns。Java 获胜! (2认同)

Nit*_*art 38

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

  • 创建一个JMH项目
  • 写一小段可矢量数学.
  • 在-XX:-UseSuperWord和-XX:+ UseSuperWord(默认)之间运行基准翻转
  • 如果没有观察到性能差异,那么您的代码可能无法进行矢量化
  • 为了确保,运行您的基准测试,以便打印出组件.在linux上你可以享受perfasm profiler(' - profasm')看一看,看看你期望的指令是否会产生.

例:

@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
}
Run Code Online (Sandbox Code Playgroud)

带和不带标志的结果(最近的Haswell笔记本电脑,Oracle JDK 8u60): - XX:+ UseSuperWord:475.073±44.579 ns/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
Run Code Online (Sandbox Code Playgroud)

玩得开心冲进城堡!

  • 让我们翻一下这个问题,询问JVM是否会对任何算术运算进行自动向量化?你能提供一个例子吗?我有一个循环,我不得不拔出并最近使用内在函数重写.但是,我希望看到对显式向量化/内在函数的支持(类似于http://www.agner.org/optimize/vectorclass.pdf),而不是希望自动向量化.更好的方法是为Aparapi编写一个好的Java后端(虽然该项目的领导有一些错误的目标).你在JVM上工作吗? (2认同)

Ved*_*ran 26

在以Java 7u40开头的HotSpot版本中,服务器编译器提供对自动矢量化的支持.根据JDK-6340864

然而,这似乎只适用于"简单循环" - 至少目前是这样.例如,累积数组不能进行矢量化JDK-7192383

  • 由于英特尔的贡献,HotSpot中的编译器矢量化支持最近(2017年6月)得到了很大改善.性能方面尚未发布的jdk9(b163及更高版本)目前胜过jdk8,因为启用了AVX2的错误修复.循环必须满足自动向量化的一些约束才能工作,例如使用:int计数器,常量计数器增量,带循环不变量的一个终止条件,没有方法调用的循环体(?),没有手动循环展开!详情请参阅:http://cr.openjdk.java.net/~vlivanov/talks/2017_Vectorization_in_HotSpot_JVM.pdf (3认同)
  • 使用 AVX2 指令通过循环向量化演示整数 4 倍加速的小型基准测试:http://prestodb.rocks/code/simd/ (2认同)

kok*_*ing 5

这篇文章是关于试验我的朋友写的Java和SIMD指令的好文章:http: //prestodb.rocks/code/simd/

它的一般结果是你可以期望JIT在1.8中使用一些SSE操作(以及1.9中的更多操作).虽然你不应该期待太多,但你需要小心.