diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index 632a81d5798..7f131d55ffd 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; @@ -407,8 +408,7 @@ static Type monovm_typemap_java_to_managed (string java_type_name) static readonly Type[] XAConstructorSignature = new Type [] { typeof (IntPtr), typeof (JniHandleOwnership) }; static readonly Type[] JIConstructorSignature = new Type [] { typeof (JniObjectReference).MakeByRefType (), typeof (JniObjectReferenceOptions) }; - static readonly Dictionary ActivationConstructorCache = new Dictionary (); - static readonly Lock ActivationConstructorCacheLock = new Lock (); + static readonly ConcurrentDictionary ActivationConstructorCache = new ConcurrentDictionary (1, 3); enum ActivationConstructorKind { @@ -419,6 +419,23 @@ enum ActivationConstructorKind readonly record struct ActivationConstructor (ConstructorInfo? Constructor, ActivationConstructorKind Kind); + /// + /// Preserves constructor annotations through the stateful GetOrAdd factory, + /// whose Func key parameter cannot carry . + /// + readonly struct AnnotatedType + { + public AnnotatedType ( + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type type) + { + Type = type; + } + + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] + public Type Type { get; } + } + internal static object CreateProxy ( [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type type, @@ -452,23 +469,18 @@ static ActivationConstructor GetActivationConstructor ( [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type type) { - lock (ActivationConstructorCacheLock) { - if (ActivationConstructorCache.TryGetValue (type, out var activation)) - return activation; - - const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; - var constructor = type.GetConstructor (flags, null, XAConstructorSignature, null); - if (constructor != null) { - activation = new ActivationConstructor (constructor, ActivationConstructorKind.XA); - } else { - constructor = type.GetConstructor (flags, null, JIConstructorSignature, null); - activation = new ActivationConstructor ( + return ActivationConstructorCache.GetOrAdd (type, + static (_, state) => { + const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + var constructor = state.Type.GetConstructor (flags, null, XAConstructorSignature, null); + if (constructor != null) + return new ActivationConstructor (constructor, ActivationConstructorKind.XA); + + constructor = state.Type.GetConstructor (flags, null, JIConstructorSignature, null); + return new ActivationConstructor ( constructor, constructor == null ? ActivationConstructorKind.Missing : ActivationConstructorKind.JI); - } - ActivationConstructorCache.Add (type, activation); - return activation; - } + }, new AnnotatedType (type)); } static IJavaPeerable GetUninitializedObject ( diff --git a/tests/Android.Benchmarks/ActivationConstructorCacheBenchmarks.cs b/tests/Android.Benchmarks/ActivationConstructorCacheBenchmarks.cs new file mode 100644 index 00000000000..e4acc594416 --- /dev/null +++ b/tests/Android.Benchmarks/ActivationConstructorCacheBenchmarks.cs @@ -0,0 +1,63 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using Android.Runtime; +using BenchmarkDotNet.Attributes; + +namespace Xamarin.Android.Benchmarks; + +public class ActivationConstructorCacheBenchmarks +{ + delegate object CreateProxyDelegate (Type type, IntPtr handle, JniHandleOwnership transfer); + + readonly CreateProxyDelegate createProxy; + IntPtr globalReference; + + public ActivationConstructorCacheBenchmarks () + { + const BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Static; + var method = typeof (Java.Interop.TypeManager).GetMethod ( + "CreateProxy", + flags, + null, + [typeof (Type), typeof (IntPtr), typeof (JniHandleOwnership)], + null); + if (method == null) + throw new InvalidOperationException ("Could not find TypeManager.CreateProxy."); + + createProxy = method.CreateDelegate (); + } + + [GlobalSetup] + public void Setup () + { + IntPtr reference = JNIEnv.NewString ("benchmark"); + try { + globalReference = JNIEnv.NewGlobalRef (reference); + } finally { + JNIEnv.DeleteLocalRef (reference); + } + _ = CreateProxy (); + } + + [GlobalCleanup] + public void Cleanup () + { + JNIEnv.DeleteGlobalRef (globalReference); + } + + [Benchmark] + public int CreateProxy () + { + IntPtr reference = JNIEnv.NewLocalRef (globalReference); + Java.Lang.String? proxy = null; + try { + proxy = (Java.Lang.String) createProxy (typeof (Java.Lang.String), reference, JniHandleOwnership.TransferLocalRef); + reference = IntPtr.Zero; + return RuntimeHelpers.GetHashCode (proxy); + } finally { + if (reference != IntPtr.Zero) + JNIEnv.DeleteLocalRef (reference); + proxy?.Dispose (); + } + } +}