比方说,我的Java程序的瓶颈还真是有些紧张的循环来计算一堆向量的点积。 是的,我已经成型,是它的瓶颈,是的,它是显著,没错这就是算法只是怎么回事,是的,我已经运行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
配备英特尔酷睿i7-3632QM CPU @ 2.20GHz的Fedora 20,GCC 4.8.3,和的OpenJDK 7或8,我得到这样的输出:
dot(): 37 ns
dotc(): 23 ns
或约1.6倍的速度。 我们需要使用的,而不是直接的阵列NIO的缓冲区,但热点能够快速访问直接NIO的缓冲区为数组 。 在另一方面,手动展开循环,不提供性能可测量提升,在这种情况下。
为了解决一些别人在这里我建议任何人谁想要证明自己或其他使用下面的方法表示怀疑态度的:
- 创建江铃控股项目
- 写向量化数学的一个小片段。
- 运行-XX之间的基准翻转:-UseSuperWord和-XX:+ UseSuperWord(默认)
- 如果在性能上没有差异观察,你的代码可能没有得到矢量
- 为了确保运行基准测试,使得打印出的组件。 在Linux上,你可以享受perfasm分析器(“ - 教授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笔记本电脑,甲骨文JDK 8u60):-XX:+ UseSuperWord:475.073±44.579纳秒/ OP(每运纳秒)-XX:-UseSuperWord:3376.364±233.211纳秒/运算
对于热循环的组件是有点多格式,并坚持在这里,但这里有一个片段(hsdis.so失败格式化一些AVX2向量指令,所以我跑了-XX:UseAVX = 1):-XX:+ UseSuperWord(与 '-prof perfasm:intelSyntax =真')
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开始,服务器编译器提供了自动向量化支持。 根据JDK-6340864
不过,这似乎只是为“简单的循环”是真实的 - 至少在那一刻。 例如,累积的阵列还不能矢量化JDK-7192383
以下是有关与我的朋友写的Java和SIMD指令实验好文章: http://prestodb.rocks/code/simd/
其总的结果是,你可以期望JIT在1.8使用一些SSE操作(多一些1.9)。 虽然你不应该期望太多,你需要小心。
你可以写的OpenCL内核做了计算,并从Java运行它http://www.jocl.org/ 。
代码可以在CPU和/或GPU上运行和OpenCL语言还支持矢量类型的,所以你应该能够采取明确的优势如SSE3 / 4条指令。
我猜你写了这个问题,你发现了NETLIB的Java之前;-)它到底提供您所需要的原生API,与机优化的实现,而不必在由于得益于内存钉扎天然边界的任何费用。
看一看用于计算微内核的优化实现Java和JNI的性能比较 。 他们表明的Java HotSpot虚拟机服务器的编译器支持使用超字级并行,这是仅限于的循环并行内简单的情况下,自动向量化。 这篇文章也会给你一些指导你的数据大小是否大到足以证明去JNI路线。
我不相信如果多数任何VM遇上聪明足以让这种最佳化的。 为了公平起见最优化技术要简单得多,如转移,而不是乘法whena的两个动力。 Mono项目推出自己的矢量等多种方式与本机背衬,以帮助提高性能。
文章来源: Do any JVM's JIT compilers generate code that uses vectorized floating point instructions?