Skip to content

Commit 9a64fef

Browse files
committed
[README] Add P/Invoke-based JNI invocation notes.
Related: the pinvoke-jnienv branch.
1 parent 513f8e5 commit 9a64fef

File tree

1 file changed

+61
-0
lines changed

1 file changed

+61
-0
lines changed

README.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,64 @@ the case in Xamarin.Android -- then the overhead isn't actually that bad,
171171
on a per-method invoke basis. It appears to only get really bad when
172172
invoking the same method repetitively, *a lot*, which I believe shouldn't
173173
be *that* common a use case (outside of image manipulation?).
174+
175+
### JNI and P/Invoke
176+
177+
[Commit 9d2dfc5][9d2dfc5] observed that there's a fair bit of overhead
178+
associated with using `SafeHandle`s, in large parge because `SafeHandle`s
179+
need to be [*thread safe*][cbrumme-SafeHandle] in order to prevent
180+
[handle recycling attacks][handle-recycle]. Xamarin.Android doesn't
181+
suffer from handle recycling attacks *only* because Mono's SGEN GC
182+
conservatively scans the stack, prolonging the lifetime of all temporaries
183+
found there. If/when Xamarin.Android moves to a precise GC for the stack,
184+
this will no longer be the case and handle recycling attacks -- along
185+
with possibly finalizing/`Dispose()`ing of instances
186+
*while they're still being used* -- can become "a thing".
187+
188+
[9d2dfc5]: https://github.com/xamarin/Java.Interop/commit/9d2dfc5
189+
[cbrumme-SafeHandle]: http://blogs.msdn.com/b/cbrumme/archive/2004/02/20/77460.aspx
190+
[handle-recycle]: http://blogs.msdn.com/b/cbrumme/archive/2003/04/19/51365.aspx
191+
192+
An idea that came to mind to reduce the overhead of `SafeHandle` use was to
193+
P/Invoke to a native library to perform the `JNIEnv` function pointer
194+
invocations instead of using `delegate` invocations alongside
195+
`Marshal.GetDelegateForFunctionPointer()`, as is currently the case.
196+
197+
This was implemented in the [pinvoke-jnienv][pinvoke-jnienv] branch,
198+
in [commit 802842a3][802842a3].
199+
200+
[pinvoke-jnienv]: https://github.com/xamarin/Java.Interop/commits/pinvoke-jnienv
201+
[802842a3]: https://github.com/xamarin/Java.Interop/commit/802842a361380812e290fe3585fea8c0a7a19b97
202+
203+
The result: Using P/Invoke *increases* invocation overhead:
204+
205+
# "Full" Invocations: JNIEnv::CallObjectMethod() + JNIEnv::DeleteLocalRef() for 10000 iterations
206+
Java.Interop Object.toString() Timing: 00:00:30.0774575; 3.00774575 ms/iteration -- ~386.391119190154x
207+
Xamarin.Android Object.toString() Timing: 00:00:00.0778420; 0.0077842 ms/iteration
208+
# JNIEnv::CallObjectMethod() for 500 iterations
209+
Java.Interop Object.toString() Timing: 00:00:00.8041310; 1.608262 ms/CallVirtualObjectMethod() -- ~266.648207712969x
210+
Xamarin.Android CallObjectMethod() Timing: 00:00:00.0030157; 0.0060314 ms/CallObjectMethod()
211+
# JNIEnv::DeleteLocalRef() for 500 iterations
212+
Java.Interop JniLocalReference.Dispose() Timing: 00:00:00.8979645; 1.795929 ms/Dispose() -- ~1661.05160932297x
213+
Xamarin.Android DeleteLocalRef() Timing: 00:00:00.0005406; 0.0010812 ms/DeleteLocalRef()
214+
## Breaking down the above Object.toString() + JniLocalReference.Dispose() timings, the JNI calls:
215+
# JNIEnv::CallObjectMethod: SafeHandle vs. IntPtr
216+
Java.Interop safeCall() Timing: 00:00:00.0058775; 0.011755 ms/SafeHandle JNIEnv::CallObjectMethodA() -- ~2.14538618776464x
217+
Java.Interop P/Invoke safeCall() Timing: 00:00:00.0069479; 0.0138958 ms/SafeHandle JNIEnv::CallObjectMethodA() -- ~2.53610016060739x
218+
Java.Interop unsafeCall() Timing: 00:00:00.0027396; 0.0054792 ms/IntPtr JNIEnv::CallObjectMethodA()
219+
# JNIEnv::DeleteLocalRef: SafeHandle vs. IntPtr
220+
Java.Interop safeDel() Timing: 00:00:00.0006010; 0.001202 ms/SafeHandle JNIEnv::DeleteLocalRef() -- ~1.47412312975227x
221+
Java.Interop P/Invoke safeDel() Timing: 00:00:00.0007480; 0.001496 ms/SafeHandle JNIEnv::DeleteLocalRef() -- ~1.83468236448369x
222+
Java.Interop unsafeDel() Timing: 00:00:00.0004077; 0.0008154 ms/IntPtr JNIEnv::DeleteLocalRef
223+
224+
In particular, note the `Java.Interop P/Invoke` lines:
225+
226+
Java.Interop P/Invoke safeCall() Timing: 00:00:00.0069479; 0.0138958 ms/SafeHandle JNIEnv::CallObjectMethodA() -- ~2.53610016060739x
227+
Java.Interop P/Invoke safeDel() Timing: 00:00:00.0007480; 0.001496 ms/SafeHandle JNIEnv::DeleteLocalRef() -- ~1.83468236448369x
228+
229+
Compare to the `SafeHandle`-using delegate-based invocations:
230+
231+
Java.Interop safeCall() Timing: 00:00:00.0058775; 0.011755 ms/SafeHandle JNIEnv::CallObjectMethodA() -- ~2.14538618776464x
232+
Java.Interop safeDel() Timing: 00:00:00.0006010; 0.001202 ms/SafeHandle JNIEnv::DeleteLocalRef() -- ~1.47412312975227x
233+
234+
Surprisingly, using delegates results in less overhead than using P/Invoke.

0 commit comments

Comments
 (0)