From 25d83dd042ca714452df4b8d8a3c57655921aea7 Mon Sep 17 00:00:00 2001 From: Manodasan Wignarajah Date: Tue, 15 Feb 2022 19:50:42 -0800 Subject: [PATCH] Improve non agile object performance (#1106) * CompareExchange * Add benchark for non agile objects * Add another benchmark and cleanup code * Minor changes * Agile reference optimizations * Variable rename * Add back fallback Co-authored-by: Joshua Larkin <70237359+j0shuams@users.noreply.github.com> --- src/Benchmarks/NonAgileObjectPerf.cs | 66 ++++++++ src/WinRT.Runtime/AgileReference.cs | 15 +- .../Interop/IAgileReference.net5.cs | 148 ++++++++---------- .../Interop/IAgileReference.netstandard2.0.cs | 55 +++++-- src/WinRT.Runtime/ObjectReference.cs | 86 +++++----- 5 files changed, 231 insertions(+), 139 deletions(-) create mode 100644 src/Benchmarks/NonAgileObjectPerf.cs diff --git a/src/Benchmarks/NonAgileObjectPerf.cs b/src/Benchmarks/NonAgileObjectPerf.cs new file mode 100644 index 000000000..a945e409d --- /dev/null +++ b/src/Benchmarks/NonAgileObjectPerf.cs @@ -0,0 +1,66 @@ +using BenchmarkDotNet.Attributes; +using System.Threading; + +namespace Benchmarks +{ + [MemoryDiagnoser] + public class NonAgileObjectPerf + { + AutoResetEvent createObject; + AutoResetEvent exitThread; + AutoResetEvent objectCreated; + Thread staThread; + private volatile Windows.UI.Popups.PopupMenu nonAgileObject; + + [GlobalSetup] + public void Setup() + { + createObject = new AutoResetEvent(false); + exitThread = new AutoResetEvent(false); + objectCreated = new AutoResetEvent(false); + staThread = new Thread(new ThreadStart(ObjectAllocationLoop)); + staThread.SetApartmentState(ApartmentState.STA); + staThread.Start(); + } + + [GlobalCleanup] + public void Cleanup() + { + exitThread.Set(); + createObject.Set(); + } + + private void ObjectAllocationLoop() + { + while (createObject.WaitOne() && !exitThread.WaitOne(1)) + { + createObject.Reset(); + nonAgileObject = new Windows.UI.Popups.PopupMenu(); + CallObject(); + objectCreated.Set(); + } + } + + private int CallObject() + { + return nonAgileObject.Commands.Count; + } + + [Benchmark] + public void ConstructAndQueryNonAgileObject() + { + createObject.Set(); + objectCreated.WaitOne(); + CallObject(); + objectCreated.Reset(); + } + + [Benchmark] + public void ConstructNonAgileObject() + { + createObject.Set(); + objectCreated.WaitOne(); + objectCreated.Reset(); + } + } +} diff --git a/src/WinRT.Runtime/AgileReference.cs b/src/WinRT.Runtime/AgileReference.cs index 41c992a2c..efffe18a8 100644 --- a/src/WinRT.Runtime/AgileReference.cs +++ b/src/WinRT.Runtime/AgileReference.cs @@ -16,7 +16,7 @@ class AgileReference : IDisposable { private readonly static Guid CLSID_StdGlobalInterfaceTable = new(0x00000323, 0, 0, 0xc0, 0, 0, 0, 0, 0, 0, 0x46); private readonly static Lazy Git = new Lazy(() => GetGitTable()); - private readonly IAgileReference _agileReference; + private readonly IObjectReference _agileReference; private readonly IntPtr _cookie; private bool disposed; @@ -35,12 +35,8 @@ public unsafe AgileReference(IObjectReference instance) 0 /*AGILEREFERENCE_DEFAULT*/, ref iid, instance.ThisPtr, - &agileReference)); -#if NET - _agileReference = (IAgileReference)new SingleInterfaceOptimizedObject(typeof(IAgileReference), ObjectReference.Attach(ref agileReference)); -#else - _agileReference = ABI.WinRT.Interop.IAgileReference.FromAbi(agileReference).AsType(); -#endif + &agileReference)); + _agileReference = ObjectReference.Attach(ref agileReference); } catch(TypeLoadException) { @@ -51,7 +47,10 @@ public unsafe AgileReference(IObjectReference instance) MarshalInterface.DisposeAbi(agileReference); } } - public IObjectReference Get() => _cookie == IntPtr.Zero ? _agileReference?.Resolve(IUnknownVftbl.IID) : Git.Value?.GetInterfaceFromGlobal(_cookie, IUnknownVftbl.IID); + + public IObjectReference Get() => _cookie == IntPtr.Zero ? ABI.WinRT.Interop.IAgileReferenceMethods.Resolve(_agileReference, IUnknownVftbl.IID) : Git.Value?.GetInterfaceFromGlobal(_cookie, IUnknownVftbl.IID); + + internal ObjectReference Get(Guid iid) => _cookie == IntPtr.Zero ? ABI.WinRT.Interop.IAgileReferenceMethods.Resolve(_agileReference, iid) : Git.Value?.GetInterfaceFromGlobal(_cookie, IUnknownVftbl.IID)?.As(iid); protected virtual void Dispose(bool disposing) { diff --git a/src/WinRT.Runtime/Interop/IAgileReference.net5.cs b/src/WinRT.Runtime/Interop/IAgileReference.net5.cs index a860fc784..2ebcdb9ea 100644 --- a/src/WinRT.Runtime/Interop/IAgileReference.net5.cs +++ b/src/WinRT.Runtime/Interop/IAgileReference.net5.cs @@ -10,7 +10,7 @@ namespace WinRT.Interop [Guid("C03F6A43-65A4-9818-987E-E0B810D2A6F2")] internal interface IAgileReference { - IObjectReference Resolve(Guid riid); + IObjectReference Resolve(Guid riid); } [WindowsRuntimeType] @@ -37,107 +37,95 @@ internal interface IGlobalInterfaceTable namespace ABI.WinRT.Interop { - using global::WinRT; - - [DynamicInterfaceCastableImplementation] - [Guid("C03F6A43-65A4-9818-987E-E0B810D2A6F2")] - internal unsafe interface IAgileReference : global::WinRT.Interop.IAgileReference - { - [Guid("C03F6A43-65A4-9818-987E-E0B810D2A6F2")] - public struct Vftbl + using global::WinRT; + + internal static class IAgileReferenceMethods + { + public static unsafe IObjectReference Resolve(IObjectReference _obj, Guid riid) { - public global::WinRT.Interop.IUnknownVftbl IUnknownVftbl; - private void* _Resolve; - public delegate* unmanaged[Stdcall] Resolve { get => (delegate* unmanaged[Stdcall])_Resolve; set => _Resolve = value; } - - public static readonly Vftbl AbiToProjectionVftable; - public static readonly IntPtr AbiToProjectionVftablePtr; + if (_obj == null) return null; -#if !NET - public delegate int ResolveDelegate(IntPtr thisPtr, Guid* riid, IntPtr* objectReference); - private static readonly Delegate[] DelegateCache = new Delegate[1]; -#endif - static Vftbl() + var ThisPtr = _obj.ThisPtr; + IntPtr ptr = IntPtr.Zero; + ExceptionHelpers.ThrowExceptionForHR((*(delegate* unmanaged[Stdcall]**)ThisPtr)[3]( + ThisPtr, &riid, &ptr)); + try { - AbiToProjectionVftable = new Vftbl - { - IUnknownVftbl = global::WinRT.Interop.IUnknownVftbl.AbiToProjectionVftbl, -#if !NET - _Resolve = Marshal.GetFunctionPointerForDelegate(DelegateCache[0] = new ResolveDelegate(Do_Abi_Resolve)).ToPointer(), -#else - _Resolve = (delegate* unmanaged)&Do_Abi_Resolve -#endif - }; - AbiToProjectionVftablePtr = Marshal.AllocHGlobal(Marshal.SizeOf()); - Marshal.StructureToPtr(AbiToProjectionVftable, AbiToProjectionVftablePtr, false); + return ComWrappersSupport.GetObjectReferenceForInterface(ptr); } - -#if NET - [UnmanagedCallersOnly] -#endif - private static int Do_Abi_Resolve(IntPtr thisPtr, Guid* riid, IntPtr* objectReference) + finally { - IObjectReference _objectReference = default; - - *objectReference = default; - - try - { - _objectReference = global::WinRT.ComWrappersSupport.FindObject(thisPtr).Resolve(*riid); - *objectReference = _objectReference?.GetRef() ?? IntPtr.Zero; - } - catch (Exception __exception__) - { - return __exception__.HResult; - } - return 0; + MarshalInspectable.DisposeAbi(ptr); } - } - - public static ObjectReference FromAbi(IntPtr thisPtr) => ObjectReference.FromAbi(thisPtr); + } + + public static unsafe ObjectReference Resolve(IObjectReference _obj, Guid riid) + { + if (_obj == null) return null; - IObjectReference global::WinRT.Interop.IAgileReference.Resolve(Guid riid) - { - var _obj = ((ObjectReference)((IWinRTObject)this).GetObjectReferenceForType(typeof(global::WinRT.Interop.IAgileReference).TypeHandle)); var ThisPtr = _obj.ThisPtr; - - ExceptionHelpers.ThrowExceptionForHR(_obj.Vftbl.Resolve(ThisPtr, ref riid, out IntPtr ptr)); + IntPtr ptr = IntPtr.Zero; + ExceptionHelpers.ThrowExceptionForHR((*(delegate* unmanaged[Stdcall]**)ThisPtr)[3]( + ThisPtr, &riid, &ptr)); try { - return ComWrappersSupport.GetObjectReferenceForInterface(ptr); + return ComWrappersSupport.GetObjectReferenceForInterface(ptr); } finally { MarshalInspectable.DisposeAbi(ptr); } + } + } + + [DynamicInterfaceCastableImplementation] + [Guid("C03F6A43-65A4-9818-987E-E0B810D2A6F2")] + internal unsafe interface IAgileReference : global::WinRT.Interop.IAgileReference + { + public static IntPtr AbiToProjectionVftablePtr; + static unsafe IAgileReference() + { + AbiToProjectionVftablePtr = ComWrappersSupport.AllocateVtableMemory(typeof(IAgileReference), sizeof(global::WinRT.Interop.IUnknownVftbl) + sizeof(IntPtr) * 1); + *(global::WinRT.Interop.IUnknownVftbl*)AbiToProjectionVftablePtr = global::WinRT.Interop.IUnknownVftbl.AbiToProjectionVftbl; + ((delegate* unmanaged*)AbiToProjectionVftablePtr)[3] = &Do_Abi_Resolve; + } + + [UnmanagedCallersOnly] + private static int Do_Abi_Resolve(IntPtr thisPtr, Guid* riid, IntPtr* objectReference) + { + IObjectReference _objectReference = default; + + *objectReference = default; + + try + { + _objectReference = global::WinRT.ComWrappersSupport.FindObject(thisPtr).Resolve(*riid); + *objectReference = _objectReference?.GetRef() ?? IntPtr.Zero; + } + catch (Exception __exception__) + { + return __exception__.HResult; + } + return 0; } + + IObjectReference global::WinRT.Interop.IAgileReference.Resolve(Guid riid) + { + var _obj = ((IWinRTObject)this).GetObjectReferenceForType(typeof(global::WinRT.Interop.IAgileReference).TypeHandle); + return IAgileReferenceMethods.Resolve(_obj, riid); + } } [DynamicInterfaceCastableImplementation] [Guid("94ea2b94-e9cc-49e0-c0ff-ee64ca8f5b90")] interface IAgileObject : global::WinRT.Interop.IAgileObject - { - [Guid("94ea2b94-e9cc-49e0-c0ff-ee64ca8f5b90")] - public struct Vftbl - { - public global::WinRT.Interop.IUnknownVftbl IUnknownVftbl; - - public static readonly Vftbl AbiToProjectionVftable; - public static readonly IntPtr AbiToProjectionVftablePtr; - - static Vftbl() - { - AbiToProjectionVftable = new Vftbl - { - IUnknownVftbl = global::WinRT.Interop.IUnknownVftbl.AbiToProjectionVftbl, - }; - AbiToProjectionVftablePtr = Marshal.AllocHGlobal(Marshal.SizeOf()); - Marshal.StructureToPtr(AbiToProjectionVftable, AbiToProjectionVftablePtr, false); - } + { + public static IntPtr AbiToProjectionVftablePtr; + static unsafe IAgileObject() + { + AbiToProjectionVftablePtr = ComWrappersSupport.AllocateVtableMemory(typeof(IAgileObject), sizeof(global::WinRT.Interop.IUnknownVftbl)); + *(global::WinRT.Interop.IUnknownVftbl*)AbiToProjectionVftablePtr = global::WinRT.Interop.IUnknownVftbl.AbiToProjectionVftbl; } - - public static ObjectReference FromAbi(IntPtr thisPtr) => ObjectReference.FromAbi(thisPtr); - } [Guid("00000146-0000-0000-C000-000000000046")] diff --git a/src/WinRT.Runtime/Interop/IAgileReference.netstandard2.0.cs b/src/WinRT.Runtime/Interop/IAgileReference.netstandard2.0.cs index 872b82b8e..1052770e2 100644 --- a/src/WinRT.Runtime/Interop/IAgileReference.netstandard2.0.cs +++ b/src/WinRT.Runtime/Interop/IAgileReference.netstandard2.0.cs @@ -10,7 +10,7 @@ namespace WinRT.Interop [Guid("C03F6A43-65A4-9818-987E-E0B810D2A6F2")] internal interface IAgileReference { - IObjectReference Resolve(Guid riid); + IObjectReference Resolve(Guid riid); } [WindowsRuntimeType] @@ -36,7 +36,46 @@ internal interface IGlobalInterfaceTable namespace ABI.WinRT.Interop { - using global::WinRT; + using global::WinRT; + + internal static class IAgileReferenceMethods + { + public static unsafe IObjectReference Resolve(IObjectReference _obj, Guid riid) + { + if (_obj == null) return null; + + var ThisPtr = _obj.ThisPtr; + IntPtr ptr = IntPtr.Zero; + ExceptionHelpers.ThrowExceptionForHR((*(delegate* unmanaged[Stdcall]**)ThisPtr)[3]( + ThisPtr, &riid, &ptr)); + try + { + return ComWrappersSupport.GetObjectReferenceForInterface(ptr); + } + finally + { + MarshalInspectable.DisposeAbi(ptr); + } + } + + public static unsafe ObjectReference Resolve(IObjectReference _obj, Guid riid) + { + if (_obj == null) return null; + + var ThisPtr = _obj.ThisPtr; + IntPtr ptr = IntPtr.Zero; + ExceptionHelpers.ThrowExceptionForHR((*(delegate* unmanaged[Stdcall]**)ThisPtr)[3]( + ThisPtr, &riid, &ptr)); + try + { + return ComWrappersSupport.GetObjectReferenceForInterface(ptr); + } + finally + { + MarshalInspectable.DisposeAbi(ptr); + } + } + } [Guid("C03F6A43-65A4-9818-987E-E0B810D2A6F2")] internal sealed unsafe class IAgileReference : global::WinRT.Interop.IAgileReference @@ -104,16 +143,8 @@ public IAgileReference(ObjectReference obj) public IObjectReference Resolve(Guid riid) { - ExceptionHelpers.ThrowExceptionForHR(_obj.Vftbl.Resolve(ThisPtr, ref riid, out IntPtr ptr)); - try - { - return ComWrappersSupport.GetObjectReferenceForInterface(ptr); - } - finally - { - MarshalInspectable.DisposeAbi(ptr); - } - } + return IAgileReferenceMethods.Resolve(_obj, riid); + } } [Guid("94ea2b94-e9cc-49e0-c0ff-ee64ca8f5b90")] diff --git a/src/WinRT.Runtime/ObjectReference.cs b/src/WinRT.Runtime/ObjectReference.cs index 1db4fdd18..592006146 100644 --- a/src/WinRT.Runtime/ObjectReference.cs +++ b/src/WinRT.Runtime/ObjectReference.cs @@ -448,8 +448,33 @@ internal sealed class ObjectReferenceWithContext : ObjectReference { private readonly IntPtr _contextCallbackPtr; private readonly IntPtr _contextToken; - private readonly Lazy>> _cachedContext; - private readonly Lazy _agileReference; + + private volatile ConcurrentDictionary> __cachedContext; + private ConcurrentDictionary> CachedContext => __cachedContext ?? Make_CachedContext(); + private ConcurrentDictionary> Make_CachedContext() + { + global::System.Threading.Interlocked.CompareExchange(ref __cachedContext, new(), null); + return __cachedContext; + } + + // Agile reference can be null, so whether it is set is tracked separately. + private volatile bool _isAgileReferenceSet; + private volatile AgileReference __agileReference; + private AgileReference AgileReference => _isAgileReferenceSet ? __agileReference : Make_AgileReference(); + private AgileReference Make_AgileReference() + { + Context.CallInContext(_contextCallbackPtr, _contextToken, InitAgileReference, null); + + // Set after CallInContext callback given callback can fail to occur. + _isAgileReferenceSet = true; + return __agileReference; + + void InitAgileReference() + { + global::System.Threading.Interlocked.CompareExchange(ref __agileReference, new AgileReference(this), null); + } + } + private readonly Guid _iid; internal ObjectReferenceWithContext(IntPtr thisPtr, IntPtr contextCallbackPtr, IntPtr contextToken) @@ -457,36 +482,11 @@ internal ObjectReferenceWithContext(IntPtr thisPtr, IntPtr contextCallbackPtr, I { _contextCallbackPtr = contextCallbackPtr; _contextToken = contextToken; - _cachedContext = new Lazy>>(); - _agileReference = new Lazy(() => { - AgileReference agileReference = null; - Context.CallInContext(_contextCallbackPtr, _contextToken, InitAgileReference, null); - return agileReference; - - void InitAgileReference() - { - agileReference = new AgileReference(this); - } - }); } internal ObjectReferenceWithContext(IntPtr thisPtr, IntPtr contextCallbackPtr, IntPtr contextToken, Guid iid) - : base(thisPtr) + : this(thisPtr, contextCallbackPtr, contextToken) { - _contextCallbackPtr = contextCallbackPtr; - _contextToken = contextToken; - _cachedContext = new Lazy>>(); - _agileReference = new Lazy(() => { - AgileReference agileReference = null; - Context.CallInContext(_contextCallbackPtr, _contextToken, InitAgileReference, null); - return agileReference; - - void InitAgileReference() - { - agileReference = new AgileReference(this); - } - }); - _iid = iid; } @@ -524,11 +524,11 @@ private ObjectReference GetCurrentContext() return null; } - return _cachedContext.Value.GetOrAdd(currentContext, CreateForCurrentContext); + return CachedContext.GetOrAdd(currentContext, CreateForCurrentContext); ObjectReference CreateForCurrentContext(IntPtr _) { - var agileReference = _agileReference.Value; + var agileReference = AgileReference; // We may fail to switch context and thereby not get an agile reference. // In these cases, fallback to using the current context. if (agileReference == null) @@ -536,23 +536,31 @@ ObjectReference CreateForCurrentContext(IntPtr _) return null; } - using var referenceInContext = agileReference.Get(); - if (_iid == Guid.Empty) - { - return referenceInContext.TryAs(out var objRef) >= 0 ? objRef : null; + try + { + if (_iid == Guid.Empty) + { + return agileReference.Get(GuidGenerator.GetIID(typeof(T))); + } + else + { + return agileReference.Get(_iid); + } } - else - { - return referenceInContext.TryAs(_iid, out var objRef) >= 0 ? objRef : null; + catch (Exception) + { + // Fallback to using the current context in case of error. + return null; } } } protected override unsafe void Release() { - if (_cachedContext.IsValueCreated) + // Don't initialize cached context by calling through property if it is already null. + if (__cachedContext != null) { - _cachedContext.Value.Clear(); + CachedContext.Clear(); } Context.CallInContext(_contextCallbackPtr, _contextToken, base.Release, ReleaseWithoutContext);