Skip to content

Commit 926e4bc

Browse files
committed
[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.
1 parent 8c52dbd commit 926e4bc

File tree

11 files changed

+12520
-13965
lines changed

11 files changed

+12520
-13965
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
bin
22
obj
3+
LocalJDK
34
TestResult.xml

Makefile

+23-2
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,30 @@ clean:
2222
$(XBUILD) /t:Clean
2323
rm -Rf bin/$(CONFIGURATION)
2424

25-
bin/$(CONFIGURATION)/libNativeTiming.dylib: tests/NativeTiming/timing.c
25+
JDK = JavaDeveloper.pkg
26+
JDK_URL = http://adcdownload.apple.com/Developer_Tools/java_for_os_x_2013005_developer_package/java_for_os_x_2013005_dp__11m4609.dmg
27+
28+
LOCAL_JDK_HEADERS = LocalJDK/System/Library/Frameworks/JavaVM.framework/Versions/A/Headers
29+
30+
osx-setup: $(LOCAL_JDK_HEADERS)/jni.h
31+
32+
$(LOCAL_JDK_HEADERS)/jni.h:
33+
@if [ ! -f $(JDK) ]; then \
34+
echo "Please download '$(JDK)', from: $(JDK_URL)" ; \
35+
exit 1; \
36+
fi
37+
-mkdir LocalJDK
38+
_jdk="$$(cd `dirname "$(JDK)"`; pwd)/`basename "$(JDK)"`" ; \
39+
(cd LocalJDK; xar -xf $$_jdk)
40+
(cd LocalJDK; gunzip -c JavaEssentialsDev.pkg/Payload | cpio -i)
41+
42+
bin/$(CONFIGURATION)/libNativeTiming.dylib: tests/NativeTiming/timing.c $(LOCAL_JDK_HEADERS)/jni.h
43+
mkdir -p `dirname "$@"`
44+
gcc -g -shared -o $@ $< -m32 -I $(LOCAL_JDK_HEADERS)
45+
46+
bin/$(CONFIGURATION)/libJavaInterop.dylib: JniEnvironment.g.c $(LOCAL_JDK_HEADERS)/jni.h
2647
mkdir -p `dirname "$@"`
27-
gcc -g -shared -o $@ $< -m32 -I /System/Library/Frameworks/JavaVM.framework/Headers
48+
gcc -g -shared -o $@ $< -m32 -I $(LOCAL_JDK_HEADERS)
2849

2950
bin/$(CONFIGURATION)/Java.Interop-Tests.dll: $(wildcard src/Java.Interop/*/*.cs src/Java.Interop/Tests/*/*.cs)
3051
$(XBUILD)

README.md

+8-16
Original file line numberDiff line numberDiff line change
@@ -43,25 +43,17 @@ At the time of this writing, this links to
4343

4444
[osx-jdk6]: http://adcdownload.apple.com/Developer_Tools/java_for_os_x_2013005_developer_package/java_for_os_x_2013005_dp__11m4609.dmg
4545

46-
*Furthermore*, if running on Yosemite you must *also* download the latest
47-
[Java for OS X package](http://support.apple.com/downloads/#java), currently
48-
[JavaForOSX2014-001.dmg](http://support.apple.com/downloads/DL1572/en_US/JavaForOSX2014-001.dmg).
46+
Unfortunately, you can't *install* it on El Capitan. It'll install...but it
47+
won't *do* anything, probably because of [System Integrity Protection][sip].
4948

50-
Once download, you need to "remove" any previously installed Java packages, as
51-
the Developer package won't install over a newer runtime packages:
49+
[sip]: https://en.wikipedia.org/wiki/System_Integrity_Protection
5250

53-
sudo mv /System/Library/Frameworks/JavaVM.framework /System/Library/Frameworks/JavaVM.framework-Yosemite
51+
To develop on El Capitan, download the above
52+
`java_for_os_x_2013005_dp__11m4609.dmg` file, open it within Finder,
53+
copy the contained `JavaDeveloper.pkg` file into this directory,
54+
then run the `osx-setup` target:
5455

55-
Then install the Developer Package `java_for_os_x_2013005_dp__11m4609.dmg`,
56-
then install the runtime package `JavaForOSX2014-001.dmg`.
57-
58-
If you fail to re-install the runtime package, then `jar` will fail to run:
59-
60-
$ jar cf "../../bin/Debug/java-interop.jar" -C "../../bin/Debug/ji-classes" .
61-
java.lang.AssertionError: Platform not recognized
62-
at sun.nio.fs.DefaultFileSystemProvider.create(DefaultFileSystemProvider.java:73)
63-
at java.nio.file.FileSystems$DefaultFileSystemHolder.getDefaultProvider(FileSystems.java:108)
64-
...
56+
$ make osx-setup JDK=JavaDeveloper.pkg
6557

6658

6759
## Type Safety

src/Java.Interop/Java.Interop/JniEnvironment.cs

+11
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,17 @@ public Exception GetExceptionForLastThrowable ()
260260
JniEnvironment.Current.LogCreateLocalRef (e);
261261
return JavaVM.GetExceptionForThrowable (ref e, JniObjectReferenceOptions.DisposeSourceReference);
262262
}
263+
264+
public Exception GetExceptionForLastThrowable (IntPtr thrown)
265+
{
266+
if (thrown == IntPtr.Zero)
267+
return null;
268+
var e = new JniObjectReference (thrown, JniObjectReferenceType.Local);
269+
// JniEnvironment.Errors.ExceptionDescribe ();
270+
JniEnvironment.Exceptions.ExceptionClear ();
271+
JniEnvironment.Current.LogCreateLocalRef (e);
272+
return JavaVM.GetExceptionForThrowable (ref e, JniObjectReferenceOptions.DisposeSourceReference);
273+
}
263274
}
264275
}
265276

tests/invocation-overhead/Makefile

+20-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,26 @@
1-
all: test-overheads.exe test-overheads-xa.exe
1+
JNIENV_GEN = ../../bin/Debug/jnienv-gen.exe
2+
LOCAL_JDK_HEADERS = ../../LocalJDK/System/Library/Frameworks/JavaVM.framework/Versions/A/Headers
3+
4+
all: test-overheads.exe libJavaInterop.dylib
5+
6+
clean:
7+
-rm test-overheads.exe test-overheads.exe.mdb
8+
-rm -Rf libJavaInterop.dylib*
9+
10+
HANDLE_FEATURES = \
11+
-d:FEATURE_HANDLES_ARE_SAFE_HANDLES \
12+
-d:FEATURE_HANDLES_ARE_INTPTRS \
13+
-d:FEATURE_HANDLES_ARE_INTPTRS_WITH_PINVOKES_EX \
14+
-d:FEATURE_HANDLES_ARE_XA_INTPTRS
215

316
test-overheads.exe: test-overheads.cs jni.cs
4-
mcs -out:$@ -unsafe -d:FEATURE_HANDLES_ARE_SAFE_HANDLES -d:FEATURE_HANDLES_ARE_INTPTRS $^
17+
mcs -out:$@ -unsafe $(HANDLE_FEATURES) $^
18+
19+
jni.c jni.cs: $(JNIENV_GEN)
20+
mono $< jni.cs jni.c
521

6-
test-overheads-xa.exe: test-overheads.cs jni-xa.cs
7-
mcs -out:$@ -unsafe -d:XA -d:FEATURE_HANDLES_ARE_SAFE_HANDLES -d:FEATURE_HANDLES_ARE_INTPTRS $^
22+
libJavaInterop.dylib: jni.c
23+
gcc -g -shared -o $@ $< -m32 -I $(LOCAL_JDK_HEADERS)
824

925
run:
1026
mono --debug test-overheads.exe

0 commit comments

Comments
 (0)