Skip to content

Commit

Permalink
Improve the performance of invoking events (microsoft#1042)
Browse files Browse the repository at this point in the history
* Event improvements

* More optimizations.

* Switch to ThreadStatic

* Move to a version of CreateCCW that avoids creating an object to do a QI

* Remove benchmark which keeps adding 100s of events to the same event source

* Add comment.

* PR feedback.

* Fix build
  • Loading branch information
manodasanW authored Nov 10, 2021
1 parent 8954084 commit ec2966b
Show file tree
Hide file tree
Showing 27 changed files with 270 additions and 92 deletions.
103 changes: 103 additions & 0 deletions src/Benchmarks/EventPerf.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using BenchmarkComponent;
using BenchmarkDotNet.Attributes;
using System;

namespace Benchmarks
{
[MemoryDiagnoser]
public class EventPerf
{
ClassWithMarshalingRoutines instance;
int z2;
ClassWithMarshalingRoutines instance2;
int z4;

[GlobalSetup]
public void Setup()
{
instance = new ClassWithMarshalingRoutines();
System.EventHandler<int> s = (object sender, int value) => z2 = value;
instance.IntPropertyChanged += s;

instance2 = new ClassWithMarshalingRoutines();
System.EventHandler<int> s2 = (object sender, int value) =>
{
if (sender == this)
z4 = value;
else
z4 = value * 3;
};
instance2.IntPropertyChanged += s2;
}

[Benchmark]
public object IntEventOverhead()
{
ClassWithMarshalingRoutines instance = new ClassWithMarshalingRoutines();
int z;
System.EventHandler<int> s = (object sender, int value) => z = value;
return instance;
GC.KeepAlive(s);
}

[Benchmark]
public object AddIntEventToNewEventSource()
{
ClassWithMarshalingRoutines instance = new ClassWithMarshalingRoutines();
int z;
System.EventHandler<int> s = (object sender, int value) => z = value;
instance.IntPropertyChanged += s;
return instance;
}

[Benchmark]
public object AddMultipleEventsToNewEventSource()
{
ClassWithMarshalingRoutines instance = new ClassWithMarshalingRoutines();
int z;
double y;
System.EventHandler<int> s = (object sender, int value) => z = value;
instance.IntPropertyChanged += s;
System.EventHandler<double> t = (object sender, double value) => y = value;
instance.DoublePropertyChanged += t;
return instance;
}

[Benchmark]
public object AddAndInvokeIntEventOnNewEventSource()
{
ClassWithMarshalingRoutines instance = new ClassWithMarshalingRoutines();
int z;
System.EventHandler<int> s = (object sender, int value) => z = value;
instance.IntPropertyChanged += s;
instance.RaiseIntChanged();
return instance;
}

[Benchmark]
public int InvokeIntEvent()
{
instance.RaiseIntChanged();
return z2;
}

[Benchmark]
public int InvokeIntEventWithSenderCheck()
{
instance2.RaiseIntChanged();
return z4;
}

[Benchmark]
public object AddAndRemoveIntEventOnNewEventSource()
{
ClassWithMarshalingRoutines instance = new ClassWithMarshalingRoutines();
int z;
System.EventHandler<int> s = (object sender, int value) => z = value;
instance.IntPropertyChanged += s;
instance.IntPropertyChanged -= s;
return instance;
GC.KeepAlive(s);
}
}
}
6 changes: 6 additions & 0 deletions src/Benchmarks/QueryInterface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ public int ConstructAndQueryNonDefaultInterfaceFirstCall()
{
ClassWithMultipleInterfaces instance2 = new ClassWithMultipleInterfaces();
return instance2.IntProperty;
}

[Benchmark]
public int StaticPropertyCall()
{
return Windows.System.Power.PowerManager.RemainingChargePercent;
}
}
}
21 changes: 20 additions & 1 deletion src/WinRT.Runtime/ComWrappersSupport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,19 @@ private static Func<IInspectable, object> CreateReferenceCachingFactory(Func<IIn
_boxedValueReferenceCache.Add(resultingObject, inspectable);
return resultingObject;
};
}

private static Func<IInspectable, object> CreateCustomTypeMappingFactory(Type customTypeHelperType)
{
var fromAbiMethod = customTypeHelperType.GetMethod("FromAbi", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
if (fromAbiMethod is null)
{
throw new MissingMethodException();
}

var parms = new[] { Expression.Parameter(typeof(IInspectable), "obj") };
return Expression.Lambda<Func<IInspectable, object>>(
Expression.Call(fromAbiMethod, Expression.Property(parms[0], "ThisPtr")), parms).Compile();
}

internal static Func<IInspectable, object> CreateTypedRcwFactory(string runtimeClassName)
Expand All @@ -336,13 +349,19 @@ internal static Func<IInspectable, object> CreateTypedRcwFactory(string runtimeC
}

Type implementationType = TypeNameSupport.FindTypeByNameCached(runtimeClassName);
if(implementationType == null)
if (implementationType == null)
{
// If we reach here, then we couldn't find a type that matches the runtime class name.
// Fall back to using IInspectable directly.
return (IInspectable obj) => obj;
}

var customHelperType = Projections.FindCustomHelperTypeMapping(implementationType, true);
if (customHelperType != null)
{
return CreateReferenceCachingFactory(CreateCustomTypeMappingFactory(customHelperType));
}

if (implementationType.IsGenericType && implementationType.GetGenericTypeDefinition() == typeof(System.Collections.Generic.KeyValuePair<,>))
{
return CreateReferenceCachingFactory(CreateKeyValuePairFactory(implementationType));
Expand Down
22 changes: 18 additions & 4 deletions src/WinRT.Runtime/ComWrappersSupport.net5.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ private static DefaultComWrappers DefaultComWrappersInstance
}

internal static readonly ConditionalWeakTable<Type, InspectableInfo> InspectableInfoTable = new ConditionalWeakTable<Type, InspectableInfo>();
internal static readonly ThreadLocal<Type> CreateRCWType = new ThreadLocal<Type>();
[ThreadStatic] internal static Type CreateRCWType;

private static ComWrappers _comWrappers;
private static object _comWrappersLock = new object();
Expand Down Expand Up @@ -96,11 +96,11 @@ private static T CreateRcwForComObject<T>(IntPtr ptr, bool tryUseCache)
// when we are called by the ComWrappers API to create the object. We can't pass this through the
// ComWrappers API surface, so we are achieving it via a thread local. We unset it after in case
// there is other calls to it via other means.
CreateRCWType.Value = typeof(T);
CreateRCWType = typeof(T);

var flags = tryUseCache ? CreateObjectFlags.TrackerObject : CreateObjectFlags.TrackerObject | CreateObjectFlags.UniqueInstance;
var rcw = ComWrappers.GetOrCreateObjectForComInstance(ptr, flags);
CreateRCWType.Value = null;
CreateRCWType = null;
// Because .NET will de-duplicate strings and WinRT doesn't,
// our RCW factory returns a wrapper of our string instance.
// This ensures that ComWrappers never sees the same managed object for two different
Expand Down Expand Up @@ -170,6 +170,20 @@ public static IObjectReference CreateCCWForObject(object obj)
{
IntPtr ccw = ComWrappers.GetOrCreateComInterfaceForObject(obj, CreateComInterfaceFlags.TrackerSupport);
return ObjectReference<IUnknownVftbl>.Attach(ref ccw);
}

internal static ObjectReference<T> CreateCCWForObject<T>(object obj, Guid iid)
{
IntPtr ccw = ComWrappers.GetOrCreateComInterfaceForObject(obj, CreateComInterfaceFlags.TrackerSupport);
try
{
Marshal.ThrowExceptionForHR(Marshal.QueryInterface(ccw, ref iid, out var iidCcw));
return ObjectReference<T>.Attach(ref iidCcw);
}
finally
{
MarshalInspectable<object>.DisposeAbi(ccw);
}
}

public static unsafe T FindObject<T>(IntPtr ptr)
Expand Down Expand Up @@ -457,7 +471,7 @@ private static object CreateObject(IObjectReference objRef)
{
IInspectable inspectable = new IInspectable(inspectableRef);

string runtimeClassName = ComWrappersSupport.GetRuntimeClassForTypeCreation(inspectable, ComWrappersSupport.CreateRCWType.Value);
string runtimeClassName = ComWrappersSupport.GetRuntimeClassForTypeCreation(inspectable, ComWrappersSupport.CreateRCWType);
if (string.IsNullOrEmpty(runtimeClassName))
{
// If the external IInspectable has not implemented GetRuntimeClassName,
Expand Down
8 changes: 8 additions & 0 deletions src/WinRT.Runtime/ComWrappersSupport.netstandard2.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,14 @@ public static IObjectReference CreateCCWForObject(object obj)
var objRef = ObjectReference<IUnknownVftbl>.FromAbi(wrapper.IdentityPtr);
GC.KeepAlive(wrapper); // This GC.KeepAlive ensures that a newly created wrapper is alive until objRef is created and has AddRef'd the CCW.
return objRef;
}

internal static ObjectReference<T> CreateCCWForObject<T>(object obj, Guid iid)
{
var wrapper = ComWrapperCache.GetValue(obj, _ => new ComCallableWrapper(obj));
Marshal.ThrowExceptionForHR(Marshal.QueryInterface(wrapper.IdentityPtr, ref iid, out var iidCcw));
GC.KeepAlive(wrapper); // This GC.KeepAlive ensures that a newly created wrapper is alive until objRef is created and has AddRef'd the CCW.
return ObjectReference<T>.Attach(ref iidCcw);
}

public static T FindObject<T>(IntPtr thisPtr)
Expand Down
11 changes: 11 additions & 0 deletions src/WinRT.Runtime/Interop/IUnknownVftbl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,16 @@ unsafe struct IUnknownVftbl
public static IntPtr AbiToProjectionVftblPtr => ComWrappersSupport.IUnknownVftblPtr;

internal static readonly Guid IID = new(0, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);

// Avoids boxing when using default Equals.
internal bool Equals(IUnknownVftbl other)
{
return _QueryInterface == other._QueryInterface && _AddRef == other._AddRef && _Release == other._Release;
}

internal unsafe static bool IsReferenceToManagedObject(IntPtr ptr)
{
return (**(IUnknownVftbl**)ptr).Equals(AbiToProjectionVftbl);
}
}
}
9 changes: 3 additions & 6 deletions src/WinRT.Runtime/Interop/IWeakReferenceSource.net5.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,9 @@ public IObjectReference Resolve(Guid riid)
if (!_ref.TryGetTarget(out object target))
{
return null;
}

using (IObjectReference objReference = ComWrappersSupport.CreateCCWForObject(target))
{
return objReference.As(riid);
}
}

return ComWrappersSupport.CreateCCWForObject<IUnknownVftbl>(target, riid);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,9 @@ public IObjectReference Resolve(Guid riid)
if (!_ref.TryGetTarget(out object target))
{
return null;
}

using (IObjectReference objReference = ComWrappersSupport.CreateCCWForObject(target))
{
return objReference.As(riid);
}
}

return ComWrappersSupport.CreateCCWForObject<IUnknownVftbl>(target, riid);
}
}
}
Expand Down
40 changes: 18 additions & 22 deletions src/WinRT.Runtime/Marshalers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1150,18 +1150,16 @@ public static IObjectReference CreateMarshaler(T o, bool unwrapObject = true)
}
var publicType = o.GetType();
Type helperType = Projections.FindCustomHelperTypeMapping(publicType, true);
if(helperType != null)
if (helperType != null)
{
var parms = new[] { Expression.Parameter(typeof(object), "arg") };
var createMarshaler = Expression.Lambda<Func<object, IObjectReference>>(
Expression.Call(helperType.GetMethod("CreateMarshaler", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static),
new[] { Expression.Convert(parms[0], publicType) }), parms).Compile();
return createMarshaler(o);
}
using (var ccw = ComWrappersSupport.CreateCCWForObject(o))
{
return ccw.As<IInspectable.Vftbl>(IInspectable.IID);
}

return ComWrappersSupport.CreateCCWForObject<IInspectable.Vftbl>(o, IInspectable.IID);
}

public static IntPtr GetAbi(IObjectReference objRef) =>
Expand All @@ -1173,24 +1171,24 @@ public static T FromAbi(IntPtr ptr)
{
return default;
}
using var objRef = ObjectReference<IUnknownVftbl>.FromAbi(ptr);
using var unknownObjRef = objRef.As<IUnknownVftbl>(IUnknownVftbl.IID);
if (unknownObjRef.IsReferenceToManagedObject)
{
return (T) ComWrappersSupport.FindObject<object>(unknownObjRef.ThisPtr);
}
else if (Projections.TryGetMarshalerTypeForProjectedRuntimeClass<T>(objRef, out Type type))

IntPtr iunknownPtr = IntPtr.Zero;
try
{
var fromAbiMethod = type.GetMethod("FromAbi", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
if (fromAbiMethod is null)
Guid iid_iunknown = IUnknownVftbl.IID;
Marshal.QueryInterface(ptr, ref iid_iunknown, out iunknownPtr);
if (IUnknownVftbl.IsReferenceToManagedObject(iunknownPtr))
{
throw new MissingMethodException();
return (T)ComWrappersSupport.FindObject<object>(iunknownPtr);
}
else
{
return ComWrappersSupport.CreateRcwForComObject<T>(ptr);
}
return (T) fromAbiMethod.Invoke(null, new object[] { ptr });
}
else
finally
{
return ComWrappersSupport.CreateRcwForComObject<T>(ptr);
DisposeAbi(iunknownPtr);
}
}

Expand Down Expand Up @@ -1251,10 +1249,8 @@ public static IObjectReference CreateMarshaler(object o, Guid delegateIID, bool
{
return objRef.As<global::WinRT.Interop.IDelegateVftbl>(delegateIID);
}
using (var ccw = ComWrappersSupport.CreateCCWForObject(o))
{
return ccw.As<global::WinRT.Interop.IDelegateVftbl>(delegateIID);
}

return ComWrappersSupport.CreateCCWForObject<global::WinRT.Interop.IDelegateVftbl>(o, delegateIID);
}
}

Expand Down
26 changes: 19 additions & 7 deletions src/WinRT.Runtime/Projections/EventHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,16 @@ public static IntPtr FromManaged(global::System.EventHandler<T> managedDelegate)
private static unsafe int Do_Abi_Invoke<TAbi>(void* thisPtr, IntPtr sender, TAbi args)
{
try
{
global::WinRT.ComWrappersSupport.MarshalDelegateInvoke(new IntPtr(thisPtr), (global::System.Delegate invoke) =>
{
invoke.DynamicInvoke(MarshalInspectable<object>.FromAbi(sender), Marshaler<T>.FromAbi(args));
{
#if NET
var invoke = ComWrappersSupport.FindObject<global::System.EventHandler<T>>(new IntPtr(thisPtr));
invoke.Invoke(MarshalInspectable<object>.FromAbi(sender), Marshaler<T>.FromAbi(args));
#else
global::WinRT.ComWrappersSupport.MarshalDelegateInvoke(new IntPtr(thisPtr), (global::System.EventHandler<T> invoke) =>
{
invoke.Invoke(MarshalInspectable<object>.FromAbi(sender), Marshaler<T>.FromAbi(args));
});
#endif
}
catch (global::System.Exception __exception__)
{
Expand Down Expand Up @@ -253,13 +258,20 @@ public static IntPtr FromManaged(global::System.EventHandler managedDelegate) =>
private static unsafe int Do_Abi_Invoke(IntPtr thisPtr, IntPtr sender, IntPtr args)
{
try
{
global::WinRT.ComWrappersSupport.MarshalDelegateInvoke(thisPtr, (global::System.Delegate invoke) =>
{
#if NET
var invoke = ComWrappersSupport.FindObject<global::System.EventHandler>(thisPtr);
invoke.Invoke(
MarshalInspectable<object>.FromAbi(sender),
MarshalInspectable<object>.FromAbi(args) as EventArgs ?? EventArgs.Empty);
#else
global::WinRT.ComWrappersSupport.MarshalDelegateInvoke(thisPtr, (global::System.EventHandler invoke) =>
{
invoke.DynamicInvoke(
invoke.Invoke(
MarshalInspectable<object>.FromAbi(sender),
MarshalInspectable<object>.FromAbi(args) as EventArgs ?? EventArgs.Empty);
});
#endif
}
catch (global::System.Exception __exception__)
{
Expand Down
Loading

0 comments on commit ec2966b

Please sign in to comment.