可以说,我的Java程序的瓶颈确实是一些紧密的循环,无法计算一堆矢量点积。是的,我已经进行了概要分析,是的,它是瓶颈,是的,它很重要,是的,这就是算法的方式,是的,我运行了Proguard来优化字节码,等等。
实质上,这是点产品。float[50]与之类似,我有两个,我需要计算成对乘积之和。我知道处理器指令集可以像SSE或MMX一样快速且批量地执行此类操作。
float[50]
是的,我可能可以通过在JNI中编写一些本机代码来访问它们。JNI调用结果非常昂贵。
我知道您不能保证JIT将编译或不编译。有没有人 曾经 听说过使用这些指令的JIT生成的代码?如果是这样,那么有关Java代码的任何内容都可以使它以这种方式编译吗?
可能是“否”;值得一问。
因此,基本上,您希望代码运行得更快。JNI就是答案。我知道您说这对您不起作用,但让我向您展示您错了。
这里是Dot.java:
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:
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
使用2.80GHz @ Fedora 30,GCC 9.1.1和OpenJDK 8或11的Intel®Core™i7-7700HQ CPU,可以获得以下输出:
dot(): 39 ns dotc(): 16 ns
或大约快2.4倍。我们需要使用直接NIO缓冲区而不是数组,但是HotSpot可以像访问数组一样快地访问直接NIO缓冲区。另一方面,在这种情况下,手动展开循环不会显着提高性能。