From 048158d1a2c7711ce6177bad5ec540ee70f39461 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Thu, 11 Feb 2021 17:45:23 -0500 Subject: [PATCH] [invocation-overhead] Fix; .NET Core support `tests/invocation-overhead` partially bitrot since it was last touched in 8602581: it insta-crashes: % make run mono64 --debug=casts test-overheads.exe 2021-02-11 17:33:49.479 mono64[90565:6542595] CheckForInstalledJavaRuntimes: Please visit http://www.java.com for information on installing java. make: *** [run] Error 97 The primary "cause" is d1cce190: it was never a good idea to P/Invoke into `jvm.dll`, as: * The name for `jvm.dll` differs between platforms! It's `jvm` some places, `jre` others, and `jli` in still others! If we want (eventual) .NET Framework/.NET Core support, this is a non-starter. * The actual on-disk path is also highly variable. The solution to this conundrum was to update the `java-interop` native library to have a new `java_interop_jvm_load()` export, and use *that* to load the JVM. (Actual determination of which file to load is left "elsewhere"; once the JVM to load is *found*, then we can sanely P/Invoke into `java_interop_jvm_load()`.) This change never made it to `tests/invocation-overhead`. Rework `tests/invocation-overhead` so that it's *less* "stand-alone": it now uses `JreRuntime` from `Java.Runtime.Environment.dll` to create an in-process JVM, using the `$JI_JVM_PATH` environment variable to determine *which* JVM to load. (This is how `tests/TestJVM` works.) This change requires "re-structuring" how certain types such as `JniObjectReference` work, as it means we must now reference `Java.Interop.dll`, which *also* defines `JniObjectReference`/etc. Square this circle by moving various types into the appropriate sub-namespaces, e.g. `Java.Interop.SafeHandles` has its own special `JniObjectReference` declaration. "While we're at it", what's .NET Core's JNI invocation performance look like? Update `invocation-overhead` to multitarget net472 and netcoreapp3.1. Update `src/java-interop` so that the `java-interop` native library can be built as a netcoreapp3.1 library. (This involves removing all mention of Mono.) Update `Java.Runtime.Environment` so that `MonoRuntimeValueManager` disposes the GC Bridge, not `JreRuntime`. This avoids an `EntryPointNotFoundException`, as the `java-interop` native lib doesn't provide `java_interop_gc_bridge_get_current()`. Add a new `msbuild /t:Run` target which runs `invocation-overhead` under both the desktop `$(Runtime)` & .NET Core make prepare make all make -C tests/invocation-overhead run # Runs tests under mono & .NET Core .NET Core is faster than Mono for this particular benchmark: * `SafeTiming` timing: Mono: 9.3850449 sec .NET Core: 5.1734443 sec * `XAIntPtrTiming` timing: Mono: 4.4930288 sec .NET Core: 3.1048897 sec * `JIIntPtrTiming` timing: Mono: 4.5563368 sec .NET Core: 3.4353958 sec * `JIPinvokeTiming` timing: Mono: 3.4710383 sec .NET Core: 2.7470934 sec --- Java.Interop.sln | 7 + src/Java.Interop/Java.Interop/ManagedPeer.cs | 16 +- .../Java.Interop/JreRuntime.cs | 4 - .../Java.Interop/MonoRuntimeValueManager.cs | 5 + src/java-interop/java-interop.csproj | 5 +- .../Directory.Build.targets | 46 +++ tests/invocation-overhead/Makefile | 34 +- tests/invocation-overhead/README.md | 103 +++++- ...st-overheads.cs => invocation-overhead.cs} | 350 ++++++++---------- .../invocation-overhead.csproj | 20 + .../test-overheads.exe.config | 3 - 11 files changed, 334 insertions(+), 259 deletions(-) create mode 100644 tests/invocation-overhead/Directory.Build.targets rename tests/invocation-overhead/{test-overheads.cs => invocation-overhead.cs} (77%) create mode 100644 tests/invocation-overhead/invocation-overhead.csproj delete mode 100644 tests/invocation-overhead/test-overheads.exe.config diff --git a/Java.Interop.sln b/Java.Interop.sln index 0f9dcbb2e..a986444cd 100644 --- a/Java.Interop.sln +++ b/Java.Interop.sln @@ -97,6 +97,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.SourceWriter-Tests" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Java.Interop.Localization", "src\Java.Interop.Localization\Java.Interop.Localization.csproj", "{998D178B-F4C7-48B5-BDEE-44E2F869BB22}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "invocation-overhead", "tests\invocation-overhead\invocation-overhead.csproj", "{3CF58D34-693C-408A-BFE7-BC5E4BE44A26}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Java.Interop.NamingCustomAttributes\Java.Interop.NamingCustomAttributes.projitems*{58b564a1-570d-4da2-b02d-25bddb1a9f4f}*SharedItemsImports = 5 @@ -272,6 +274,10 @@ Global {998D178B-F4C7-48B5-BDEE-44E2F869BB22}.Debug|Any CPU.Build.0 = Debug|Any CPU {998D178B-F4C7-48B5-BDEE-44E2F869BB22}.Release|Any CPU.ActiveCfg = Release|Any CPU {998D178B-F4C7-48B5-BDEE-44E2F869BB22}.Release|Any CPU.Build.0 = Release|Any CPU + {3CF58D34-693C-408A-BFE7-BC5E4BE44A26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3CF58D34-693C-408A-BFE7-BC5E4BE44A26}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3CF58D34-693C-408A-BFE7-BC5E4BE44A26}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3CF58D34-693C-408A-BFE7-BC5E4BE44A26}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -318,6 +324,7 @@ Global {C5B732C8-7AF3-41D3-B903-AEDFC392E5BA} = {0998E45F-8BCE-4791-A944-962CD54E2D80} {6CF94627-BA74-4336-88CD-7EDA20C8F292} = {271C9F30-F679-4793-942B-0D9527CB3E2F} {998D178B-F4C7-48B5-BDEE-44E2F869BB22} = {0998E45F-8BCE-4791-A944-962CD54E2D80} + {3CF58D34-693C-408A-BFE7-BC5E4BE44A26} = {271C9F30-F679-4793-942B-0D9527CB3E2F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {29204E0C-382A-49A0-A814-AD7FBF9774A5} diff --git a/src/Java.Interop/Java.Interop/ManagedPeer.cs b/src/Java.Interop/Java.Interop/ManagedPeer.cs index 2b720d41a..5a0469efb 100644 --- a/src/Java.Interop/Java.Interop/ManagedPeer.cs +++ b/src/Java.Interop/Java.Interop/ManagedPeer.cs @@ -14,6 +14,18 @@ namespace Java.Interop { [JniTypeSignature (JniTypeName)] /* static */ sealed class ManagedPeer : JavaObject { + delegate void ConstructDelegate (IntPtr jnienv, + IntPtr klass, + IntPtr n_self, + IntPtr n_assemblyQualifiedName, + IntPtr n_constructorSignature, + IntPtr n_constructorArguments); + delegate void RegisterDelegate (IntPtr jnienv, + IntPtr klass, + IntPtr n_nativeClass, + IntPtr n_assemblyQualifiedName, + IntPtr n_methods); + internal const string JniTypeName = "com/xamarin/java_interop/ManagedPeer"; @@ -25,11 +37,11 @@ static ManagedPeer () new JniNativeMethodRegistration ( "construct", ConstructSignature, - (Action) Construct), + (ConstructDelegate) Construct), new JniNativeMethodRegistration ( "registerNativeMembers", RegisterNativeMembersSignature, - (Action) RegisterNativeMembers) + (RegisterDelegate) RegisterNativeMembers) ); } diff --git a/src/Java.Runtime.Environment/Java.Interop/JreRuntime.cs b/src/Java.Runtime.Environment/Java.Interop/JreRuntime.cs index d495226dc..651f0b4d1 100644 --- a/src/Java.Runtime.Environment/Java.Interop/JreRuntime.cs +++ b/src/Java.Runtime.Environment/Java.Interop/JreRuntime.cs @@ -147,10 +147,6 @@ public override string GetCurrentManagedThreadStackTrace (int skipFrames, bool f protected override void Dispose (bool disposing) { - var bridge = NativeMethods.java_interop_gc_bridge_get_current (); - if (bridge != IntPtr.Zero) { - NativeMethods.java_interop_gc_bridge_remove_current_app_domain (bridge); - } base.Dispose (disposing); } } diff --git a/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeValueManager.cs b/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeValueManager.cs index 989bf98a6..eeb0dd5c4 100644 --- a/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeValueManager.cs +++ b/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeValueManager.cs @@ -90,6 +90,11 @@ protected override void Dispose (bool disposing) RegisteredInstances.Clear (); RegisteredInstances = null; } + + if (bridge != IntPtr.Zero) { + NativeMethods.java_interop_gc_bridge_remove_current_app_domain (bridge); + bridge = IntPtr.Zero; + } } Dictionary>> RegisteredInstances = new Dictionary>>(); diff --git a/src/java-interop/java-interop.csproj b/src/java-interop/java-interop.csproj index 44c929c58..25b8ad761 100644 --- a/src/java-interop/java-interop.csproj +++ b/src/java-interop/java-interop.csproj @@ -1,6 +1,6 @@ - net472 + net472;netcoreapp3.1 $(ToolOutputFullPath) $(BuildToolOutputFullPath) java-interop @@ -44,6 +44,9 @@ + + + diff --git a/tests/invocation-overhead/Directory.Build.targets b/tests/invocation-overhead/Directory.Build.targets new file mode 100644 index 000000000..923147232 --- /dev/null +++ b/tests/invocation-overhead/Directory.Build.targets @@ -0,0 +1,46 @@ + + + + + + + <_NativeLibsSrc Include="$(ToolOutputFullPath)\libjava-interop.*" /> + <_NativeLibsDst Include="@(_NativeLibsSrc->'$(OutputPath)%(Filename)%(Extension)')" /> + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/invocation-overhead/Makefile b/tests/invocation-overhead/Makefile index 3e724fdec..4fe8e0359 100644 --- a/tests/invocation-overhead/Makefile +++ b/tests/invocation-overhead/Makefile @@ -1,34 +1,12 @@ CONFIGURATION = Debug -JNIENV_GEN = ../../bin/BuildDebug/jnienv-gen.exe -all: test-overheads.exe libjava-interop.dylib +all: bin/$(CONFIGURATION)/net472/invocation-overheads.exe -clean: - -rm test-overheads.exe test-overheads.exe.mdb - -rm -Rf libJavaInterop.dylib* - -include ../../build-tools/scripts/mono.mk -include ../../build-tools/scripts/jdk.mk -include ../../bin/BuildDebug/JdkInfo.mk -include ../../build-tools/scripts/msbuild.mk - -$(JNIENV_GEN): - (cd ../../build-tools/jnienv-gen ; $(MSBUILD) $(MSBUILD_FLAGS) ) - -HANDLE_FEATURES = \ - -d:FEATURE_JNIENVIRONMENT_JI_INTPTRS \ - -d:FEATURE_JNIENVIRONMENT_JI_PINVOKES \ - -d:FEATURE_JNIENVIRONMENT_SAFEHANDLES \ - -d:FEATURE_JNIENVIRONMENT_XA_INTPTRS +bin/$(CONFIGURATION)/net472/invocation-overheads.exe: + msbuild /restore -test-overheads.exe: test-overheads.cs jni.cs - mcs -out:$@ -unsafe $(HANDLE_FEATURES) $^ - -jni.c jni.cs: $(JNIENV_GEN) - $(RUNTIME) $< jni.cs jni.c - -libjava-interop.dylib: jni.c - gcc -g -shared -fPIC -o $@ $< -m64 -DJI_DLL_EXPORT -fvisibility=hidden $(JI_JDK_INCLUDE_PATHS:%=-I%) +clean: + msbuild /t:Clean run: - $(RUNTIME) test-overheads.exe + msbuild /t:Run /nologo /v:m diff --git a/tests/invocation-overhead/README.md b/tests/invocation-overhead/README.md index 6c6ed3189..448f811f0 100644 --- a/tests/invocation-overhead/README.md +++ b/tests/invocation-overhead/README.md @@ -1,34 +1,63 @@ -Timing: +# JNI Invocation Overhead -The original Java.Interop effort weanted a type-safe and simple binding. As such, it usedd SafeHandles. +The original Java.Interop effort wanted a *type-safe* and *simple* +binding around JNI. As such, it used `SafeHandle`s. As the Xamarin.Forms team has turned their attention to profiling Xamarin.Forms apps, and finding major Xamarin.Android-related -performance issues, performance needs to be considered. +performance issues, performance needed to be considered. For example, GC object allocation is a MAJOR concern for them; ideally, you could have ZERO GC ALLOCATIONS performed when invoking a Java method. -SafeHandles don't fit "nicely" in that world; every method that returns a SafeHandle ALLOCATES A NEW GC OBJECT. +`SafeHandle`s don't fit "nicely" in that world; every method that +returns a `SafeHandle` ALLOCATES A NEW GC OBJECT. -So...how bad is it? +So...how bad is that? -What's in this directory is a VERY TRIMMED DOWN Java.Interop layer. -Really, it's NOT Java.Interop; it's the core generated JniEnvironment.g.cs (as `jni.cs`) -with code for both SafeHandles and IntPtr-oriented invocation strategies. +What's in this directory is insanity: there are four different "strategies" +for dealing with JNI: -The test? Invoke java.util.Arrays.binarySearch(int[], int) for 10,000,000 times. + 1. `SafeHandle` All The Things! (`SafeTiming`) -Result: + 2. Xamarin.Android JNI handling from 2011 until Xamarin.Android 6.1 (2016) + (`XAIntPtrTiming`) + + This uses `IntPtr`s *everywhere*, e.g. `JNIEnv::CallObjectMethod()` returns + an `IntPtr`. + + 3. "Happier Medium?" (`JIIntPtrTiming`) + + `IntPtr`s everywhere means it's trivial to forget that + a JNI handle is a GREF vs. an LREF vs… What if we used the same `JNIEnv` + invocation logic as `XAIntPtrTiming`, but instead of `IntPtr`s everywhere + we instead had a `JniObjectReference` structure? + + 4. "Optimize (3)" (`JIPinvokeTiming`) + + (3) was slower than (2). What if we rethought the `JNIEnv` + invocation logic and removed all the `Marshal.GetDelegateForFunctionPointer()` + invocations with normal P/Invokes? + +To compare these four strategies, `jnienv-gen.exe` was updated so that *all* +of them could be emitted into the same `.cs` file, into separate namespaces. +These "core" JNI bindings could then be used with to invoke +`java.util.Arrays.binarySearch(int[], int)`, 10,000,000 times, and compare +the results. + +Result in 2015 (commit [25de1f38][25de]): + +[25de]: https://github.com/xamarin/Java.Interop/commit/25de1f38bb6b3ef2d4c98d2d95923a4bd50d2ea0 # SafeHandle timing: 00:00:02.7913432 # Average Invocation: 0.00027913432ms - # JniObjectReference timing: 00:00:01.9809859 + # JIIntPtrTiming timing: 00:00:01.9809859 # Average Invocation: 0.00019809859ms -Basically, with a `JniObjectReference` struct-oriented approach, SafeHandles take ~1.4x as long to run. -Rephrased: the JniObjectReference struct takes 70% of the time of SafeHandles. +Basically, with a `JniObjectReference` struct-oriented approach, SafeHandles +take ~1.4x longer to run. Rephrased: the `JniObjectReference` struct takes +70% of the time of SafeHandles. Ouch. @@ -36,19 +65,21 @@ What about the current Xamarin.Android "all IntPtrs all the time!" approach? # SafeHandle timing: 00:00:02.8118485 # Average Invocation: 0.00028118485ms - # JniObjectReference timing: 00:00:02.0061727 + # XAIntPtrTiming timing: 00:00:02.0061727 # Average Invocation: 0.00020061727ms -The performance difference is comparable -- SafeHandles take ~1.4x as long to run, or -IntPtrs take ~70% as long as using SafeHandles. +The performance difference is comparable -- SafeHandles take ~1.4x as long to +run, or IntPtrs take ~70% as long as using SafeHandles. -Interesting -- but probably not *that* interesting -- is that in an absolute sense, the `JniObjectReference` -struct was *faster* than the `IntPtr` approach, even though `JniObjectReference` contains *both* an `IntPtr` -*and* an enum -- and is thus bigger! +Interesting -- but probably not *that* interesting -- is that in an absolute +sense, the `JniObjectReference` struct was *faster* than the `IntPtr` approach, +even though `JniObjectReference` contains *both* an `IntPtr` *and* an enum -- +and is thus bigger! That doesn't make any sense. -Regardless, `JniObjectReference` doesn't appear to be *slower*, and thus should be a viable option here. +Regardless, `JniObjectReference` doesn't appear to be *slower*, and thus should +be a viable option here. --- @@ -90,3 +121,35 @@ when passed as an argument to native code they'll be automagically pinned and ke The current (above) timing comparison uses `IntPtr` for arguments. We should standardize on `JniObjectReference` (again). + +## 2021 Timing Update + +How do these timings compare in 2021 on Desktop Mono (macOS)? + + # SafeTiming timing: 00:00:09.3850449 + # Average Invocation: 0.00093850449ms + # XAIntPtrTiming timing: 00:00:04.4930288 + # Average Invocation: 0.00044930288ms + # JIIntPtrTiming timing: 00:00:04.5563368 + # Average Invocation: 0.00045563368ms + # JIPinvokeTiming timing: 00:00:03.4710383 + # Average Invocation: 0.00034710383ms + +In an absolute sense, things are worse: 10e6 invocations in 2015 took 2-3sec. +Now, they're taking at least 3.5sec. + +In a relative sense, `SafeHandles` got *worse*, and takes 2.09x longer than +`XAIntPtrTiming`, and 2.7x longer than `JIPinvokeTiming`! + +What about .NET Core 3.1? After some finagling, *that* can work too! + + # SafeTiming timing: 00:00:05.1734443 + # Average Invocation: 0.00051734443ms + # XAIntPtrTiming timing: 00:00:03.1048897 + # Average Invocation: 0.00031048897ms + # JIIntPtrTiming timing: 00:00:03.4353958 + # Average Invocation: 0.00034353958ms + # JIPinvokeTiming timing: 00:00:02.7470934 + # Average Invocation: 0.00027470934000000004ms + +Relative performance is a similar story: `SafeHandle`s are slowest. diff --git a/tests/invocation-overhead/test-overheads.cs b/tests/invocation-overhead/invocation-overhead.cs similarity index 77% rename from tests/invocation-overhead/test-overheads.cs rename to tests/invocation-overhead/invocation-overhead.cs index ac83e8b8f..7f7768e91 100644 --- a/tests/invocation-overhead/test-overheads.cs +++ b/tests/invocation-overhead/invocation-overhead.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Reflection; using System.Runtime.InteropServices; using Java.Interop; @@ -8,20 +10,7 @@ using PinvokeEnv = Java.Interop.JIPinvokes.JniEnvironment; using XAIntPtrEnv = Java.Interop.XAIntPtrs.JniEnvironment; -namespace Java.Interop { - public enum JniObjectReferenceType { - Invalid = 0, - Local = 1, - Global = 2, - WeakGlobal = 3, - } - - public enum JniReleaseArrayElementsMode { - Default, - Commit, - Abort, - } - +namespace Java.Interop.SafeHandles { public struct JniObjectReference { public JniReferenceSafeHandle SafeHandle {get; private set;} @@ -48,152 +37,6 @@ public JniObjectReference (IntPtr handle, JniObjectReferenceType type = JniObjec Type = type; } } - - [StructLayout(LayoutKind.Explicit)] - public struct JniArgumentValue { -#pragma warning disable 0414 - [FieldOffset(0)] bool z; - [FieldOffset(0)] sbyte b; - [FieldOffset(0)] char c; - [FieldOffset(0)] short s; - [FieldOffset(0)] int i; - [FieldOffset(0)] long j; - [FieldOffset(0)] float f; - [FieldOffset(0)] double d; - [FieldOffset(0)] IntPtr l; -#pragma warning restore 0414 - - public JniArgumentValue (bool value) - { - this = new JniArgumentValue (); - z = value; - } - - public JniArgumentValue (sbyte value) - { - this = new JniArgumentValue (); - b = value; - } - - public JniArgumentValue (char value) - { - this = new JniArgumentValue (); - c = value; - } - - public JniArgumentValue (short value) - { - this = new JniArgumentValue (); - s = value; - } - - public JniArgumentValue (int value) - { - this = new JniArgumentValue (); - i = value; - } - - public JniArgumentValue (long value) - { - this = new JniArgumentValue (); - j = value; - } - - public JniArgumentValue (float value) - { - this = new JniArgumentValue (); - f = value; - } - - public JniArgumentValue (double value) - { - this = new JniArgumentValue (); - d = value; - } - public JniArgumentValue (IntPtr value) - { - this = new JniArgumentValue (); - l = value; - } - public JniArgumentValue (JniObjectReference value) - { - this = new JniArgumentValue (); - var sh = value.SafeHandle; - if (sh != null) - l = value.SafeHandle.DangerousGetHandle (); - else - l = value.Handle; - } - - public JniArgumentValue (JniReferenceSafeHandle value) - { - this = new JniArgumentValue (); - l = value == null ? IntPtr.Zero : value.DangerousGetHandle (); - } - - public override string ToString () - { - return string.Format ("Java.Interop.JniArgumentValue(z={0},b={1},c={2},s={3},i={4},f={5},d={6},l=0x{7})", - z, b, c, s, i, f, d, l.ToString ("x")); - } - } - public sealed class JniFieldInfo - { - public IntPtr ID; - public bool IsStatic; - public bool IsValid {get {return ID != IntPtr.Zero;}} - - public JniFieldInfo (IntPtr id, bool isStatic) - { - ID = id; - IsStatic = isStatic; - } - public JniFieldInfo (string name, string signature, IntPtr id, bool isStatic) - { - ID = id; - IsStatic = isStatic; - } - - public override string ToString () - { - return string.Format ("{0}(0x{1})", GetType ().FullName, ID.ToString ("x")); - } - } - - public class JniMethodInfo - { - public IntPtr ID; - public bool IsStatic; - public bool IsValid {get {return ID != IntPtr.Zero;}} - public JniMethodInfo (IntPtr id, bool isStatic) - { - ID = id; - IsStatic = isStatic; - } - public JniMethodInfo (string name, string signature, IntPtr id, bool isStatic) - { - ID = id; - IsStatic = isStatic; - } - public override string ToString () - { - return string.Format ("{0}(0x{1})", GetType ().FullName, ID.ToString ("x")); - } - } - public struct JniNativeMethodRegistration { - - public string Name; - public string Signature; - public Delegate Marshaler; - - public JniNativeMethodRegistration (string name, string signature, Delegate marshaler) - { - Name = name; - Signature = signature; - Marshaler = marshaler; - } - } - public abstract class JniReferenceSafeHandle : SafeHandle { protected JniReferenceSafeHandle () @@ -292,6 +135,7 @@ public override string ToString () return string.Format ("{0}(0x{1})", GetType ().FullName, handle.ToString ("x")); } } + public sealed class JavaVMSafeHandle : SafeHandle { JavaVMSafeHandle () @@ -324,27 +168,7 @@ public override string ToString () return string.Format ("{0}(0x{1})", GetType ().FullName, handle.ToString ("x")); } } - struct JavaVMInitArgs { - public JniVersion version; /* use JNI_VERSION_1_2 or later */ - - public int nOptions; - public IntPtr /* JavaVMOption[] */ options; - public byte ignoreUnrecognized; - } - - struct JavaVMOption { - public IntPtr /* const char* */ optionString; - public IntPtr /* void * */ extraInfo; - } - public enum JniVersion { - // v1_1 = 0x00010001, - v1_2 = 0x00010002, - v1_4 = 0x00010004, - v1_6 = 0x00010006, - } -} -namespace Java.Interop.SafeHandles { class JniEnvironmentInfo { public IntPtr EnvironmentPointer; public JniEnvironmentInvoker Invoker; @@ -371,6 +195,22 @@ public static Exception GetExceptionForLastThrowable () } namespace Java.Interop.JIIntPtrs { + public struct JniObjectReference + { + public IntPtr Handle {get; private set;} + public JniObjectReferenceType Type {get; private set;} + public bool IsValid { + get { + return Handle != IntPtr.Zero; + } + } + + public JniObjectReference (IntPtr handle, JniObjectReferenceType type = JniObjectReferenceType.Invalid) + { + Handle = handle; + Type = type; + } + } class JniEnvironmentInfo { public IntPtr EnvironmentPointer; public JniEnvironmentInvoker Invoker; @@ -396,6 +236,22 @@ public static Exception GetExceptionForLastThrowable () } namespace Java.Interop.JIPinvokes { + public struct JniObjectReference + { + public IntPtr Handle {get; private set;} + public JniObjectReferenceType Type {get; private set;} + public bool IsValid { + get { + return Handle != IntPtr.Zero; + } + } + + public JniObjectReference (IntPtr handle, JniObjectReferenceType type = JniObjectReferenceType.Invalid) + { + Handle = handle; + Type = type; + } + } public static partial class JniEnvironment { public static IntPtr EnvironmentPointer; @@ -426,6 +282,22 @@ public static Exception GetExceptionForLastThrowable () } } namespace Java.Interop.XAIntPtrs { + public struct JniObjectReference + { + public IntPtr Handle {get; private set;} + public JniObjectReferenceType Type {get; private set;} + public bool IsValid { + get { + return Handle != IntPtr.Zero; + } + } + + public JniObjectReference (IntPtr handle, JniObjectReferenceType type = JniObjectReferenceType.Invalid) + { + Handle = handle; + Type = type; + } + } class JniEnvironmentInfo { public IntPtr EnvironmentPointer; public JniEnvironmentInvoker Invoker; @@ -449,34 +321,110 @@ public static Exception GetExceptionForLastThrowable () } } +namespace Java.Interop { + public sealed class JniFieldInfo + { + public IntPtr ID; + public bool IsStatic; + public bool IsValid {get {return ID != IntPtr.Zero;}} -class App { - const string LibraryName = "jvm.dll"; + public JniFieldInfo (IntPtr id, bool isStatic) + { + ID = id; + IsStatic = isStatic; + } + public JniFieldInfo (string name, string signature, IntPtr id, bool isStatic) + { + ID = id; + IsStatic = isStatic; + } + + public override string ToString () + { + return string.Format ("{0}(0x{1})", GetType ().FullName, ID.ToString ("x")); + } + } + public class JniMethodInfo + { + public IntPtr ID; + public bool IsStatic; + public bool IsValid {get {return ID != IntPtr.Zero;}} + public JniMethodInfo (IntPtr id, bool isStatic) + { + ID = id; + IsStatic = isStatic; + } + public JniMethodInfo (string name, string signature, IntPtr id, bool isStatic) + { + ID = id; + IsStatic = isStatic; + } + public override string ToString () + { + return string.Format ("{0}(0x{1})", GetType ().FullName, ID.ToString ("x")); + } + } +} - [DllImport (LibraryName)] - static extern int JNI_CreateJavaVM (out IntPtr javavm, out IntPtr jnienv, ref JavaVMInitArgs args); + +class DummyValueManager : JniRuntime.JniValueManager { + public override void WaitForGCBridgeProcessing () + { + } + public override void CollectPeers () + { + } + public override void AddPeer (IJavaPeerable reference) + { + } + public override void RemovePeer (IJavaPeerable reference) + { + } + public override void FinalizePeer (IJavaPeerable reference) + { + } + public override List GetSurfacedPeers () + { + return null; + } + public override IJavaPeerable PeekPeer (global::Java.Interop.JniObjectReference reference) + { + return null; + } + public override void ActivatePeer (IJavaPeerable self, global::Java.Interop.JniObjectReference reference, ConstructorInfo cinfo, object [] argumentValues) + { + throw new NotImplementedException (); + } +} + +class DummyObjectReferenceManager : JniRuntime.JniObjectReferenceManager { + public override int GlobalReferenceCount { + get {return 0;} + } + + public override int WeakGlobalReferenceCount { + get {return 0;} + } +} + +class App { public static void Main () { - IntPtr _jvm, _env; - CreateJavaVM (out _jvm, out _env); + var runtimeOptions = new JreRuntimeOptions (){ + JvmLibraryPath = Environment.GetEnvironmentVariable ("JI_JVM_PATH"), + ValueManager = new DummyValueManager (), + ObjectReferenceManager = new DummyObjectReferenceManager (), + }; + var GlobalRuntime = runtimeOptions.CreateJreVM (); + IntPtr _env = global::Java.Interop.JniEnvironment.EnvironmentPointer; SafeTiming (_env); + XAIntPtrTiming (_env); JIIntPtrTiming (_env); JIPinvokeTiming (_env); - XAIntPtrTiming (_env); - } - static void CreateJavaVM (out IntPtr jvm, out IntPtr jnienv) - { - var args = new JavaVMInitArgs () { - version = JniVersion.v1_6, - nOptions = 0, - ignoreUnrecognized = (byte) 1, - }; - int r = JNI_CreateJavaVM (out jvm, out jnienv, ref args); - if (r != 0) - throw new InvalidOperationException ("JNI_CreateJavaVM returned: " + r); + GlobalRuntime.Dispose (); } const int C = 10000000; @@ -495,7 +443,7 @@ static unsafe void SafeTiming (IntPtr _env) var t = Stopwatch.StartNew (); var args = stackalloc JniArgumentValue [2]; - args [0] = new JniArgumentValue (intArray); + args [0] = new JniArgumentValue (intArray.SafeHandle.DangerousGetHandle ()); args [1] = new JniArgumentValue (2); for (int i = 0; i < C; ++i) { int r = SafeEnv.StaticMethods.CallStaticIntMethod (Arrays_class, Arrays_binarySearch, args); @@ -522,7 +470,7 @@ static unsafe void JIIntPtrTiming (IntPtr _env) var t = Stopwatch.StartNew (); var args = stackalloc JniArgumentValue [2]; - args [0] = new JniArgumentValue (intArray); + args [0] = new JniArgumentValue (intArray.Handle); args [1] = new JniArgumentValue (2); for (int i = 0; i < C; ++i) { int r = JIIntPtrEnv.StaticMethods.CallStaticIntMethod (Arrays_class, Arrays_binarySearch, args); @@ -543,7 +491,7 @@ static unsafe void JIPinvokeTiming (IntPtr _env) var t = Stopwatch.StartNew (); var args = stackalloc JniArgumentValue [2]; - args [0] = new JniArgumentValue (intArray); + args [0] = new JniArgumentValue (intArray.Handle); args [1] = new JniArgumentValue (2); for (int i = 0; i < C; ++i) { int r = PinvokeEnv.StaticMethods.CallStaticIntMethod (Arrays_class, Arrays_binarySearch, args); diff --git a/tests/invocation-overhead/invocation-overhead.csproj b/tests/invocation-overhead/invocation-overhead.csproj new file mode 100644 index 000000000..36117f0a1 --- /dev/null +++ b/tests/invocation-overhead/invocation-overhead.csproj @@ -0,0 +1,20 @@ + + + + Exe + net472;netcoreapp3.1 + True + True + FEATURE_JNIENVIRONMENT_JI_INTPTRS;FEATURE_JNIENVIRONMENT_JI_PINVOKES;FEATURE_JNIENVIRONMENT_SAFEHANDLES;FEATURE_JNIENVIRONMENT_XA_INTPTRS + + + + + + + + + + + + diff --git a/tests/invocation-overhead/test-overheads.exe.config b/tests/invocation-overhead/test-overheads.exe.config deleted file mode 100644 index 3d9277004..000000000 --- a/tests/invocation-overhead/test-overheads.exe.config +++ /dev/null @@ -1,3 +0,0 @@ - - -