我刚刚点燃了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);
}一个简单的链表模式还不够吗?
编辑:我最初的问题是假设实现效率很低。汉斯和乔恩以令人钦佩的坚韧不拔的精神指出了关于上述实现的适用性的几个事实(这两人在这里都很受尊敬)。
汉斯指出,在多核CPU上使用带有TrySetSlot的数组最终是对缓存友好的(从而提高了性能),而Jon善意地建立了一个微基准,这表明上述实现产生了非常可接受的性能特性。
发布于 2010-10-24 17:27:40
恰恰相反,这段代码是通过不使用List<>或类似的集合对象来优化的。清单所做的每一件事在这里都是内联的。另一个优点是锁很便宜(TrySetSlot使用Interlocked.CompareExchange),并且节省了在锁定对象周围拖动的成本。在.NET框架中,显式地内联这样的代码而不是将其留给JIT编译器并不常见。但是像这样的低级原语是例外的。
发布于 2010-10-24 15:59:59
对于这样一个基本的性能关键特性,
似乎效率极低。
您认为代表是否经常参与活动(或在其他时间合并)?
例如,在Windows窗体应用程序中,这种情况很可能很少发生--基本上在设置窗体时,大多数情况下.在这一点上,发生的事情要比MulticastDelegate.CombineImpl中的要严重得多。
经常发生的事情是.例如,对于LINQ查询中每个投影或谓词(etc)中的每个项。这才是性能的关键环节,国际海事组织。
我也不认为这段代码像你想的那么低效。在创建一个比所需的更大的数组方面,它采用了与ArrayList相同的方法,以便根据需要填充它。链接列表会更有效吗?可能在某些方面--但同样地,就地点和间接的程度而言,这也是不太有效的。(因为每个节点都需要是一个本身包含对委托的引用的新对象,所以导航列表最终可能会给内存带来比引用数组更多的页面。)
编辑:就像一个快速的微基准(附带所有通常的注意事项)一样,这里有一些代码可以执行给定次数的迭代来组合给定数量的委托:
using System;
using System.Diagnostics;
class Test
{
const int Iterations = 10000000;
const int Combinations = 3;
static void Main()
{
// Make sure all paths are JITted
Stopwatch sw = Stopwatch.StartNew();
sw.Stop();
Action tmp = null;
for (int j = 0; j < Combinations; j++)
{
tmp += Foo;
}
sw = Stopwatch.StartNew();
for (int i = 0; i < Iterations; i++)
{
Action action = null;
for (int j = 0; j < Combinations; j++)
{
action += Foo;
}
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
}
static void Foo()
{
}
}在我的机器上有一些结果,所有结果都有10,000,000次迭代:
5名代表:约5.8秒
4名代表:约4.3秒
3名代表:约3.2秒
2名代表:约1.4秒
1名代表:约160
(所有的测试都运行多次;以上只是一些有代表性的样本。我没有拿平均水平什么的。)
鉴于上述结果,我怀疑,任何路径,即使是在组合沉重的WPF,只附加一个委托,将是惊人的快速。它们会明显减慢,从1-2,然后逐渐退化(但与1-2的比例差异远小于1-2)。
https://stackoverflow.com/questions/4008911
复制相似问题