假设我有以下两种方法:
public void ConsumesDeletage (Func<object> consumer) { ... }
public object DoesSomething() { ... }
在同一课程中,我有以下第三种方法:
public void ForceDelegateConsumption()
{
ConsumesDeletage(DoesSomething);
}
每当我调用ForceDelegateConsumption()时,内部会发生什么?我是否为每次通话都创建了一个新的Func对象? JITer完成此代码后;它变成了等同于简单函数指针的东西,只需要计算一次吗?还有其他什么呢?
答案 0 :(得分:4)
每当我调用ForceDelegateConsumption()时,内部会发生什么?我是否为每次调用创建了一个新的Func对象?
是的,但这往往是一个非常便宜的操作:你只是将“方法组”转换为Func<>
。它可能比您考虑的任何替代方案都快或快。
这是一个展示的基准。时间太快了,我要包括一个no-op,以便我们可以捕获在基准测试开销中花费了多少时间:
void Main()
{
// Enter setup code here
Func<object> cachedDoesSomething = DoesSomething;
var actions = new[]
{
new TimedAction("No-op", () =>
{
}),
new TimedAction("method call", () =>
{
DoesSomething();
}),
new TimedAction("ForceDelegateConsumption", () =>
{
ForceDelegateConsumption();
}),
new TimedAction("ForceDelegateConsumptionInline", () =>
{
ConsumesDelegate(DoesSomething);
}),
new TimedAction("DoesSomethingInlined", () =>
{
ConsumesDelegate(() => null);
}),
new TimedAction("CachedLambda", () =>
{
ConsumesDelegate(cachedDoesSomething);
}),
new TimedAction("Explicit lambda", () =>
{
ConsumesDelegate(() => DoesSomething());
}),
};
const int TimesToRun = 10000000; // Tweak this as necessary
TimeActions(TimesToRun, actions);
}
public void ConsumesDelegate (Func<object> consumer) {
consumer();
}
public object DoesSomething() { return null; }
public void ForceDelegateConsumption()
{
ConsumesDelegate(DoesSomething);
}
结果:
Message DryRun1 DryRun2 FullRun1 FullRun2
No-op 0.046 0.0004 56.5705 57.3681
method call 0.1294 0.0004 96.169 98.9377
ForceDelegateConsumption 0.2555 0.0004 315.6183 284.0828
ForceDelegateConsumptionInline 0.1997 0.0012 278.4389 263.8278
DoesSomethingInlined 0.2909 0.0008 145.8749 152.2732
CachedLambda 0.1388 0.0004 125.7794 135.8126
Explicit lambda 0.2444 0.0004 308.683 304.2111
所有“FullRun”数字都是运行方法一千万次所需的毫秒数。
所以你可以看到我的基准测试框架需要50毫秒,即使我们什么都不做。简单地调用DoSomething
方法,假设它什么都不做,需要另外50毫秒来运行一千万次。然后需要额外200毫秒或更短的时间来调用ConsumesDelegate
,将DoSomething
作为方法组传递给它(也是一千万次)。
如果您编写的代码将在性能关键路径中被调用数百万次,那么您将需要考虑完全避免使用lambdas。它们增加了一点内存开销,并增加了一点额外的CPU时间。否则,我不打算避免方法组转换。
答案 1 :(得分:1)
从头开始重建并不是特别昂贵,实际上,您只是创建一个具有两个字段的新对象,即对象引用和MethodInfo
实例(或某些等效实例)。
几乎可以肯定自己不值得尝试缓存这些代表,仅仅因为它开始时是一个如此快速的操作。