MulticastDelegate.CombineImpl效率低下吗?

时间:2010-10-24 15:38:25

标签: .net performance delegates

我刚刚启动了Reflector并且窥视了MultiCastDelegate.CombineImpl并看到了一些非常冗长的代码......每次组合两个代理(读取:每次将多个事件处理程序附加到事件时),以下代码获取运行,这对于这样一个基本的性能关键特性似乎效率低下。有谁知道为什么这样写?

[SecuritySafeCritical]
protected sealed override Delegate CombineImpl(Delegate follow)
{
    object[] objArray;
    int num2;
    if (follow == null)
    {
        return this;
    }
    if (!Delegate.InternalEqualTypes(this, follow))
    {
        throw new ArgumentException(Environment.GetResourceString("Arg_DlgtTypeMis"));
    }
    MulticastDelegate o = (MulticastDelegate) follow;
    int num = 1;
    object[] objArray2 = o._invocationList as object[];
    if (objArray2 != null)
    {
        num = (int) o._invocationCount;
    }
    object[] objArray3 = this._invocationList as object[];
    if (objArray3 == null)
    {
        num2 = 1 + num;
        objArray = new object[num2];
        objArray[0] = this;
        if (objArray2 == null)
        {
            objArray[1] = o;
        }
        else
        {
            for (int i = 0; i < num; i++)
            {
                objArray[1 + i] = objArray2[i];
            }
        }
        return this.NewMulticastDelegate(objArray, num2);
    }
    int index = (int) this._invocationCount;
    num2 = index + num;
    objArray = null;
    if (num2 <= objArray3.Length)
    {
        objArray = objArray3;
        if (objArray2 == null)
        {
            if (!this.TrySetSlot(objArray, index, o))
            {
                objArray = null;
            }
        }
        else
        {
            for (int j = 0; j < num; j++)
            {
                if (!this.TrySetSlot(objArray, index + j, objArray2[j]))
                {
                    objArray = null;
                    break;
                }
            }
        }
    }
    if (objArray == null)
    {
        int length = objArray3.Length;
        while (length < num2)
        {
            length *= 2;
        }
        objArray = new object[length];
        for (int k = 0; k < index; k++)
        {
            objArray[k] = objArray3[k];
        }
        if (objArray2 == null)
        {
            objArray[index] = o;
        }
        else
        {
            for (int m = 0; m < num; m++)
            {
                objArray[index + m] = objArray2[m];
            }
        }
    }
    return this.NewMulticastDelegate(objArray, num2, true);
}

一个简单的链表模式是不是已经足够了?


编辑:我原来的问题预先假定实施效率低下。汉斯和乔恩(在SO上都是备受推崇的贡献者)具有令人钦佩的坚韧,他指出了几个关于上述实施适用性的事实。

汉斯指出,使用TrySetSlot的数组最终会在多核CPU上缓存友好(因此提高性能),而Jon则将微基准组合在一起,揭示了上述实现产生非常可接受的性能特征。

2 个答案:

答案 0 :(得分:2)

恰恰相反,不使用List&lt;&gt;这段代码优化或类似的集合对象。列表所做的一切都在这里内联。附加的优点是锁定很便宜(TrySetSlot使用Interlocked.CompareExchange)并节省了在锁定对象周围拖动的成本。明确地将这样的代码内联而不是将其留给JIT编译器在.NET框架中并不常见。但是像这样的低级原语也有例外。

答案 1 :(得分:1)

  对于这样一个基本的性能关键特性而言,这似乎效率极低

您认为代表与事件有关的频率(或在其他时间合并)?

例如,在Windows窗体应用程序中,很可能很少发生这种情况 - 基本上在设置表单时,大多数情况下...此时发生的事情远远超过{{1}中的情况。 }。

经常发生的是代理被调用 ...例如,对于LINQ查询中的每个投影或谓词(等)中的每个项目。 那是真正的性能关键位,IMO。

我也不相信这段代码和您认为的那样低效。在创建比所需数组更大的数组方面采用与MulticastDelegate.CombineImpl相同的方法,根据需要填充它。链表是否会更有效率?可能在某些方面 - 但同样地,它在地点性和间接水平方面更少有效。 (因为每个节点都需要是一个新对象,它本身包含对委托的引用,因此导航列表最终会导致更多页面进入内存而不是引用数组。)

编辑:就像一个快速的微基准测试(包含所有常见的警告),这里有一些代码可以执行给定数量的迭代来组合给定数量的委托:

ArrayList

我的机器上的一些结果,都有10,000,000次迭代:

5名代表:约5.8秒
4名代表:约4.3秒
3名代表:约3.2秒
2名代表:约1.4秒
1名代表:约160ms

(所有测试都运行多次;以上只是样本,似乎具有相当的代表性。我没有采用平均值或任何东西。)

鉴于上述结果,我怀疑任何路径即使在组合密集的WPF中,只能附加一个单个委托也会非常快。它们将从1-2逐渐减慢,然后逐渐降低(但比例差异远小于1-2的情况)。