可以在此通用代码中避免Delegate.DynamicInvoke吗?

这个问题部分是关于代表,部分是关于generics。

鉴于简化的代码:

internal sealed class TypeDispatchProcessor { private readonly Dictionary _actionByType = new Dictionary(); public void RegisterProcedure(Action action) { _actionByType[typeof(T)] = action; } public void ProcessItem(object item) { Delegate action; if (_actionByType.TryGetValue(item.GetType(), out action)) { // Can this call to DynamicInvoke be avoided? action.DynamicInvoke(item); } } } 

我在其他地方读到,直接调用委托(带括号)比调用DynamicInvokeDynamicInvoke数量级,这是有道理的。

对于上面的代码示例,我想知道我是否可以执行类型检查并以某种方式提高性能。

一些上下文:我有一个对象流,可以在各种处理程序中进行处理,这些处理程序可以在运行时注册/取消注册。 上面的模式完全符合我的目的,但如果可能的话,我想让它变得更加快捷。

一种选择是将Action存储在Dictionary ,并使用另一个委托包装Action委托。 我还没有比较第二次间接调用会影响的性能变化。

我强烈怀疑包装调用比使用DynamicInvoke更有效。 您的代码将是:

 internal sealed class TypeDispatchProcessor { private readonly Dictionary> _actionByType = new Dictionary>(); public void RegisterProcedure(Action action) { _actionByType[typeof(T)] = item => action((T) item); } public void ProcessItem(object item) { Action action; if (_actionByType.TryGetValue(item.GetType(), out action)) { action(item); } } } 

值得对它进行基准测试,但我认为你会发现它更有效率。 DynamicInvoke必须使用reflection等检查所有参数,而不是包装的委托中的简单强制转换。

所以我做了一些测量。

 var delegates = new List(); var actions = new List>(); const int dataCount = 100; const int loopCount = 10000; for (int i = 0; i < dataCount; i++) { Action a = d => { }; delegates.Add(a); actions.Add(o => a((int)o)); } var sw = Stopwatch.StartNew(); for (int i = 0; i < loopCount; i++) { foreach (var action in actions) action(i); } Console.Out.WriteLine("{0:#,##0} Action calls in {1:#,##0.###} ms", loopCount * dataCount, sw.Elapsed.TotalMilliseconds); sw = Stopwatch.StartNew(); for (int i = 0; i < loopCount; i++) { foreach (var del in delegates) del.DynamicInvoke(i); } Console.Out.WriteLine("{0:#,##0} DynamicInvoke calls in {1:#,##0.###} ms", loopCount * dataCount, sw.Elapsed.TotalMilliseconds); 

我创建了一些间接调用的项目,以避免JIT可能执行的任何优化。

结果非常引人注目!

 在47.172毫秒内进行1,000,000次动作呼叫
 1,000,000个Delegate.DynamicInvoke调用12,035.943毫秒

 1,000,000个动作调用44.686毫秒
 1,000,000个Delegate.DynamicInvoke在12,318.846 ms内调用 

因此,在这种情况下,将调用替换为DynamicInvoke以进行额外的间接调用和DynamicInvoke大约快270倍 。 所有在一天的工作。

如果需要在不使用Reflection.Emit的情况下将其扩展到包装成员调用,可以通过创建一系列可以映射类和函数参数列表或返回类型的编译器提示来实现。

基本上你需要创建lambdas,它将对象作为参数并返回一个对象。 然后使用编译器看到AOT的generics函数来创建适当方法的缓存来调用成员并转换参数。 诀窍是创建开放的委托并通过第二个lamda传递它们以在运行时获取基础提示。

您必须提供每个类和签名的提示(但不是每个方法或属性)。

我在这里做了一个这样做的课程,在这篇文章中列出的时间太长了。

在性能测试中,它没有上述示例那么好,但它是通用的,这意味着它可以在我需要的情况下工作。 与Invoke相比,读取属性时性能约为4.5倍。