11

这个问题部分是关于代表的,部分是关于泛型的。

给定简化的代码:

internal sealed class TypeDispatchProcessor
{
    private readonly Dictionary<Type, Delegate> _actionByType 
        = new Dictionary<Type, Delegate>();

    public void RegisterProcedure<T>(Action<T> 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);
        }
    }
}

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

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

一些上下文:我有一个对象流,这些对象流被分配给各种处理程序,并且这些处理程序可以在运行时注册/注销。上面的模式非常适合我的目的,但如果可能的话,我想让它更快捷。

一种选择是将代表存储Action<object>Dictionary, 并用另一个代表包装Action<T>代表。我还没有比较第二次间接调用会影响的性能变化。

4

3 回答 3

24

我强烈怀疑包装调用会比使用DynamicInvoke. 您的代码将是:

internal sealed class TypeDispatchProcessor
{
    private readonly Dictionary<Type, Action<object>> _actionByType 
        = new Dictionary<Type, Action<object>>();

    public void RegisterProcedure<T>(Action<T> action)
    {
        _actionByType[typeof(T)] = item => action((T) item);
    }

    public void ProcessItem(object item)
    {
        Action<object> action;
        if (_actionByType.TryGetValue(item.GetType(), out action))
        {
            action(item);
        }
    }
}

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

于 2009-07-12T13:54:12.760 回答
7

所以我对此做了一些测量。

var delegates = new List<Delegate>();
var actions = new List<Action<object>>();

const int dataCount = 100;
const int loopCount = 10000;

for (int i = 0; i < dataCount; i++)
{
    Action<int> 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<object> 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 可能执行的任何优化。

结果相当令人信服!

1,000,000 次动作调用在 47.172 毫秒内
1,000,000 次 Delegate.DynamicInvoke 调用在 12,035.943 毫秒内

44.686 毫秒内 1,000,000 次操作调用
1,000,000 次 Delegate.DynamicInvoke 调用在 12,318.846 毫秒内

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

于 2009-07-13T10:57:22.127 回答
1

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

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

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

我在这里开设了一个可以做到这一点的课程,在这篇文章中列出来有点太长了。

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

于 2012-04-18T21:30:22.947 回答