Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[jnienv-gen] Explore additional invocation strategies.
@migueldeicaza suggested a strategy for speeding up JNI invocations: Bundle the exception check with the method invocation. The logic here is that JNIEnv::ExceptionOccurred() should be a *fast* invocation -- it only (currently) returns a field from the JNIEnv* struct. No GC, no stopping the world, ~no overhead to speak of. Yet most of the "important" JNI invocations *always* need to call it, and because of how we designed things this invocation is through a delegate: var tmp = JniEnvironment.Current.Invoker.CallObjectMethod (JniEnvironment.Current.EnvironmentPointer, @object.SafeHandle, method.ID); var __e - JniEnvironment.Exceptions.ExceptionOccurred(); if (__e.IsValid /* or whatever... */) { JniEnvironment.Exceptions.ExceptionClear(); throw WrapThatException (__e); } return tmp; JNI ~requires that you do these two JNI invocations (or the exception will be left pending, and re-raised the next time you call into Java, and possibly abort if *another* exception is then thrown...). In the case of Xamarin.Android and the Java.Interop "SafeHandle" and "IntPtrs" backends (commit 9b85e8d), this results in a second delegate invocation and corresponding overhead. @migueldeicaza asked: could we instead call JNIEnv::ExceptionOccurred() from a C wrapper as part of the "source" JNIEnv function pointer invocation, performing two JNI calls from a custom C function (as opposed to one JNI call per C wrapper, as done in commit 802842a). The C# binding thus becomes: // C# IntPtr thrown; var tmp = JavaInterop_CallObjectMethod (JniEnvironment.Current.EnvironmentPointer, out thrown, @object.SafeHandle, method.ID); if (thrown != IntPtr.Zero) { JniEnvironment.Exceptions.ExceptionClear(); throw WrapThatException (thrown); } return tmp; While the C wrapper is: /* C */ jobject JavaInterop_CallObjectMethod (JNIEnv *env, jthrowable *_thrown, jobject object, jmethodID method) { *_thrown = NULL; jobject _r_ = (*env)->CallObjectMethod (env, object, method); *_thrown = (*env)->ExceptionOccurred (env); return _r_; } The answer: Yes, we can, and it saves ~9% vs. the IntPtr backend. Update jnienv-gen to emit this third backend alongside the previous backends, and (for good measure?) emit a *fourth* Xamarin.Android-like backend for easier/saner comparisions. The tests/invocation-overhead results: # SafeTiming timing: 00:00:08.2506653 # Average Invocation: 0.00082506653ms # JIIntPtrTiming timing: 00:00:02.4018293 # Average Invocation: 0.00024018293ms # JIPinvokeTiming timing: 00:00:02.2677633 # Average Invocation: 0.00022677633ms # XAIntPtrTiming timing: 00:00:02.3472511 # Average Invocation: 0.00023472511ms SafeTiming is the SafeHandle backend; as noted in commit 25de1f3, the performance is attrocious. JIIntPtrTiming is JniObjectReference + IntPtrs (9b85e8d). JIPinvokeTiming is @migueldeicaza's suggestion, outlined above. XAIntPtrTiming is a Xamarin.Android-like "all IntPtrs all the time!" backend, for comparison purposes. Of note is that JIIntPtrTiming -- what I was planning on using for a future Xamarin.Android integration (commit 25de1f3) -- is ~1-2% slower than XAIntPtrTiming. Not great, but acceptable. Migrating to @migueldeicaza's P/Invoke approach results in performance that's 3-4% *faster* than Xamarin.Android, and ~5-9% faster than JIIntPtrTiming. Which means the Xamarin.Android integration should use the P/Invoke implementation strategy.
- Loading branch information