我刚刚启动了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则将微基准组合在一起,揭示了上述实现产生非常可接受的性能特征。
答案 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的情况)。