将函数传递给Func<>时,您需要支付多少开销?隐含在方法调用中?

时间:2014-03-18 20:28:21

标签: c# performance delegates

假设我有以下两种方法:

    public void ConsumesDeletage (Func<object> consumer) { ... }
    public object DoesSomething() { ... }

在同一课程中,我有以下第三种方法:

    public void ForceDelegateConsumption()
    {
        ConsumesDeletage(DoesSomething);
    }

每当我调用ForceDelegateConsumption()时,内部会发生什么?我是否为每次通话都创建了一个新的Func对象? JITer完成此代码后;它变成了等同于简单函数指针的东西,只需要计算一次吗?还有其他什么呢?

2 个答案:

答案 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实例(或某些等效实例)。

几乎可以肯定自己不值得尝试缓存这些代表,仅仅因为它开始时是一个如此快速的操作。