进行JNI调用的数量开销是多less?

仅仅根据性能,大约有多less“简单”的java行是打JNI调用的同等性能?

或者试图用更具体的方式来expression这个问题,如果一个简单的java操作如

someIntVar1 = someIntVar2 + someIntVar3; 

被赋予了一个“CPU工作”指数1 ,做JNI调用的开销是什么典型的(ballpark)“CPU工作”指标?

这个问题忽略了等待本地代码执行的时间。 在电话方面,严格来讲,就是通话的“掉旗”部分,而不是“通话费率”。

问这个问题的原因是有一个“经验法则”,知道当你知道本地成本(来自直接testing)和给定操作的Java成本时,何时试图编写一个JNI调用。 它可以帮助您快速避免编写JNI调用的麻烦,只发现调用开销消耗了使用本机代码的任何好处。

编辑:

一些人正在挂起CPU,RAM等变化,这些都是几乎无关的问题 – 我要求的相对成本线的Java代码。 如果CPU和内存不足,对于Java和JNI来说都很差,所以环境因素应该平衡。 JVM版本也属于“不相关”类别。

这个问题并不是要求在毫微秒的绝对时间,而是以“简单的Java代码行”为单位的一个球场“工作努力”。

快速剖析testing产量:

Java类:

 public class Main { private static native int zero(); private static int testNative() { return Main.zero(); } private static int test() { return 0; } public static void main(String[] args) { testNative(); test(); } static { System.loadLibrary("foo"); } } 

C库:

 #include <jni.h> #include "Main.h" JNIEXPORT int JNICALL Java_Main_zero(JNIEnv *env, jobject obj) { return 0; } 

结果:

单一调用10个呼叫循环100个呼叫循环

系统详情:

 java version "1.7.0_09" OpenJDK Runtime Environment (IcedTea7 2.3.3) (7u9-2.3.3-1) OpenJDK Server VM (build 23.2-b09, mixed mode) Linux visor 3.2.0-4-686-pae #1 SMP Debian 3.2.32-1 i686 GNU/Linux 

更新:适用于x86 (32/64位)和ARMv6的 Caliper micro-benchmarks如下所示:

Java类:

 public class Main extends SimpleBenchmark { private static native int zero(); private Random random; private int[] primes; public int timeJniCall(int reps) { int r = 0; for (int i = 0; i < reps; i++) r += Main.zero(); return r; } public int timeAddIntOperation(int reps) { int p = primes[random.nextInt(1) + 54]; // >= 257 for (int i = 0; i < reps; i++) p += i; return p; } public long timeAddLongOperation(int reps) { long p = primes[random.nextInt(3) + 54]; // >= 257 long inc = primes[random.nextInt(3) + 4]; // >= 11 for (int i = 0; i < reps; i++) p += inc; return p; } @Override protected void setUp() throws Exception { random = new Random(); primes = getPrimes(1000); } public static void main(String[] args) { Runner.main(Main.class, args); } public static int[] getPrimes(int limit) { // returns array of primes under $limit, off-topic here } static { System.loadLibrary("foo"); } } 

结果(x86 / i7500 / Hotspot / Linux):

 Scenario{benchmark=JniCall} 11.34 ns; σ=0.02 ns @ 3 trials Scenario{benchmark=AddIntOperation} 0.47 ns; σ=0.02 ns @ 10 trials Scenario{benchmark=AddLongOperation} 0.92 ns; σ=0.02 ns @ 10 trials benchmark ns linear runtime JniCall 11.335 ============================== AddIntOperation 0.466 = AddLongOperation 0.921 == 

结果(amd64 / phenom 960T / Hostspot / Linux):

 Scenario{benchmark=JniCall} 6.66 ns; σ=0.22 ns @ 10 trials Scenario{benchmark=AddIntOperation} 0.29 ns; σ=0.00 ns @ 3 trials Scenario{benchmark=AddLongOperation} 0.26 ns; σ=0.00 ns @ 3 trials benchmark ns linear runtime JniCall 6.657 ============================== AddIntOperation 0.291 = AddLongOperation 0.259 = 

结果(armv6 / BCM2708 / Zero / Linux):

 Scenario{benchmark=JniCall} 678.59 ns; σ=1.44 ns @ 3 trials Scenario{benchmark=AddIntOperation} 183.46 ns; σ=0.54 ns @ 3 trials Scenario{benchmark=AddLongOperation} 199.36 ns; σ=0.65 ns @ 3 trials benchmark ns linear runtime JniCall 679 ============================== AddIntOperation 183 ======== AddLongOperation 199 ======== 

总结一下,似乎JNI调用大致相当于典型( x86 )硬件和Hotspot虚拟机上的10-25个Java操作系统。 毫不奇怪,在更less优化的零虚拟机下 ,结果是完全不同的(3-4操作)。


感谢@ Giovanni Azua和@ Marko Topolnik的参与和提示。

你应该自己testing一下“延迟”是什么。 延迟在工程中被定义为发送长度为零的消息所花费的时间。 在这种情况下,它将对应于编写调用do_nothing空C ++函数的最小Java程序,并计算经过30次测量的经过时间的平均值和stddev(执行几次额外的预热调用)。 对于不同的JDK版本和平台,您可能会对不同的平均结果感到惊讶。

只有这样做才能给你最终的答案,即使用JNI是否对你的目标环境有意义。

因此,我只是使用Eclipse Mars IDE,JDK 1.8.0_74和VirtualVM profiler 1.3.8以及Profile Startup加载项来testingWindows 8.1,64位上的C对JNI调用的“延迟”。

设置:(两种方法)
SOMETHING()传递参数,做东西,并返回参数
NOTHING()传入相同的参数,不做任何事情,并返回相同的参数。

(每个被称为270次)
SOMETHING()的总运行时间: 6523ms
NOTHING()的总运行时间: 0.102ms

因此,在我的情况下,JNI调用是微不足道的。