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

假设我的Java程序的瓶颈确实是一些紧密的循环来计算一堆向量点产品。 是的,我已经介绍过了,是的,这是瓶颈,是的,这是重要的,是的,algorithm是如何,是的,我运行Proguard优化字节码等。

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

是的,我可以通过在JNI中编写一些本机代码来访问这些代码。 JNI电话变得非常昂贵。

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

可能是“不”; 值得问。

所以,基本上,你希望你的代码运行得更快。 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 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 :

 $ javac -cp javacpp.jar Dot.java $ java -jar javacpp.jar Dot $ java -cp javacpp.jar:. Dot 

使用Intel Core i7-3632QM CPU @ 2.20GHz,Fedora 20,GCC 4.8.3和OpenJDK 7或8,我得到了这样的输出:

 dot(): 37 ns dotc(): 23 ns 

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

为了解决其他人在这里expression的一些怀疑态度,我build议任何想certificate自己或其他人使用以下方法的人:

  • 创build一个JMH项目
  • 写一个小小的vectormath片段。
  • 在-XX:-UseSuperWord和-XX之间运行基准testing:+ UseSuperWord(默认)
  • 如果没有观察到性能差异,则代码可能没有得到向量化
  • 为了确保,运行你的基准testing,打印出程序集。 在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(纳秒每个操作)-XX:-UseSuperWord:3376.364±233.211 ns / op

热循环的程序集有点格式化,并粘在这里,但这是一个片段(hsdis.so无法格式化一些AVX2vector指令,所以我用-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 

玩得开心,冲进城堡!

在从Java 7u40开始的HotSpot版本中,服务器编译器提供对自动vector化的支持。 根据JDK-6340864

然而,这似乎只是“简单的循环”,至less目前是真实的。 例如,累积数组不能被vector化JDK-7192383

您可以编写OpenCl内核来执行计算并从java http://www.jocl.org/运行它。;

代码可以在CPU和/或GPU上运行,OpenCL语言也支持vectortypes,所以你应该能够明确地利用例如SSE3 / 4指令。

我猜你在发现netlib-java之前就已经写下了这个问题;-)它提供了你所需要的本地API,以及机器优化实现,并且由于内存locking,在本地边界没有任何代价。

查看Java和JNI之间的性能比较,以获得计算微内核的最佳实现 。 他们表明,Java HotSpot VM服务器编译器支持使用超级字级并行机制的自动vector化,这仅限于循环内部并行的简单情况。 本文还将为您提供一些指导,说明您的数据大小是否足够大,以certificateJNI路由的正确性。

下面是关于用我的朋友编写的Java和SIMD指令进行实验的很好的文章: http : //prestodb.rocks/code/simd/

其总的结果是,你可以期望JIT在1.8版中使用一些SSE操作(还有一些在1.9版中)。 虽然你不应该期望太多,你需要小心。

如果有任何虚拟机足够聪明以进行这种优化,我不相信大多数。 公平地说,大多数优化要简单得多,比如在两个权力之间转移而不是乘法。 单声道项目介绍了自己的载体和其他方法与本地支持,以帮助性能。