JNI Construction Benchmark

jopen 8年前

JNI Construction Benchmark

We provide the code for a small benchmark to compare the costs of JNI object creation, and then present theresults.

The code contrasts three different approaches to constructing a Java Object that wraps a C++ object (which it has to construct). Such a scenario is common when writing a Java API wrapper for an existing C++ project.

Scenario 1 - By Call

From Java we call a JNI C++ member function to construct the C++ object and return a jlong which represents the memory pointer to the C++ object.

public class FooByCall extends NativeBackedObject {      public FooByCall() {          super();          this._nativeHandle = newFoo();      }        private native long newFoo();        ...
jlong Java_com_evolvedbinary_jni_consbench_FooByCall_newFoo(JNIEnv* env, jobject jobj) {    consbench::Foo* foo = new consbench::Foo();    return reinterpret_cast<jlong>(foo);  }

Scenario 2 - By Call, Static

Similar to Scenario 1 , except that we use a static call to a JNI C++ function.

public class FooByCallStatic extends NativeBackedObject {      public FooByCallStatic() {          super();          this._nativeHandle = newFoo();      }        private static native long newFoo();        ...
jlong Java_com_evolvedbinary_jni_consbench_FooByCallStatic_newFoo(JNIEnv* env, jclass jcls) {    consbench::Foo* foo = new consbench::Foo();    return reinterpret_cast<jlong>(foo);  }

Scenario 3 - By Call, Invoke

Similar to Scenario 1 , however instead of returning a jlong pointer, we instead in C++ find the _nativeHandle member of the calling Java object, and then directly set the long field from C++.

public class FooByCallInvoke extends NativeBackedObject {      public FooByCallInvoke() {          super();          newFoo();   //the native method, will find _nativeHandle from the class and set it directly      }        private native void newFoo();        ...
void Java_com_evolvedbinary_jni_consbench_FooByCallInvoke_newFoo(JNIEnv* env, jobject jobj) {    consbench::Foo* foo = new consbench::Foo();      //set the _nativeHandle in Java    consbench::FooByCallInvokeJni::setHandle(env, jobj, foo);  }    template<class PTR, class DERIVED> class FooJniClass {   public:    // Get the java class id    static jclass getJClass(JNIEnv* env, const char* jclazz_name) {      jclass jclazz = env->FindClass(jclazz_name);      assert(jclazz != nullptr);      return jclazz;    }      // Get the field id of the member variable to store    // the ptr    static jfieldID getHandleFieldID(JNIEnv* env) {      static jfieldID fid = env->GetFieldID(          DERIVED::getJClass(env), "_nativeHandle", "J");      assert(fid != nullptr);      return fid;    }      // Get the pointer from Java    static PTR getHandle(JNIEnv* env, jobject jobj) {      return reinterpret_cast<PTR>(          env->GetLongField(jobj, getHandleFieldID(env)));    }      // Pass the pointer to the java side.    static void setHandle(JNIEnv* env, jobject jdb, PTR ptr) {      env->SetLongField(          jdb, getHandleFieldID(env),          reinterpret_cast<jlong>(ptr));    }  };      // The portal class for com.evolvedbinary.jni.consbench.FooByCallInvoke  class FooByCallInvokeJni : public FooJniClass<consbench::Foo*, FooByCallInvokeJni> {   public:    static jclass getJClass(JNIEnv* env) {      return FooJniClass::getJClass(env,          "com/evolvedbinary/jni/consbench/FooByCallInvoke");    }  };

Results

Test machine: MacBook Pro Retina Mid-2015: 2.8 GHz Intel Core i7 / 16 GB 1600 MHz DDR3.

$ java -version  java version "1.8.0_51"  Java(TM) SE Runtime Environment (build 1.8.0_51-b16)  Java HotSpot(TM) 64-Bit Server VM (build 25.51-b03, mixed mode)

The com.evolvedbinary.jni.consbench.Benchmark class already calls each scenario 1,000,000 times, so for the benchmark we repeated this 100 times and plotted the results.

Conclusion

Scenario 2 - By Call, Static, appears to have the lowest JNI overhead for constructing C++ objects from Java.

Reproducing

If you want to run the code yourself, you need to have Java 8, Maven 3, and a C++ compiler that supports the C++ 11 standard. You can then simply run:

$ mvn clean compile package

In the target/ sub-directory, you will then find both a jni-construction-benchmark-1.0-SNAPSHOT-application folder and a jni-construction-benchmark-1.0-SNAPSHOT-application.zip file, you can use either of these. They both contain bash scripts in their bin/ sub-folders for Mac, Linux, Unix and batch scripts for Windows.

来自: https://github.com/adamretter/jni-construction-benchmark