C# 将函数传递给Func<&燃气轮机;隐式地在方法调用中?

C# 将函数传递给Func<&燃气轮机;隐式地在方法调用中?,c#,performance,delegates,C#,Performance,Delegates,假设我有以下两种方法: public void ConsumesDeletage (Func<object> consumer) { ... } public object DoesSomething() { ... } 每次调用ForceDelegateConsumption(),内部会发生什么?我是否为每个调用创建一个新的Func对象?使用此代码完成JITer后;它是否会变成一个简单的函数指针,只需计算一次?完全其他的东西?从头开始重建它并不是特别昂贵,实际上,

假设我有以下两种方法:

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

每次调用ForceDelegateConsumption(),内部会发生什么?我是否为每个调用创建一个新的Func对象?使用此代码完成JITer后;它是否会变成一个简单的函数指针,只需计算一次?完全其他的东西?

从头开始重建它并不是特别昂贵,实际上,您只是创建了一个新对象,它有两个字段,一个对象引用和一个
MethodInfo
实例(或一些等价物)


几乎可以肯定的是,自己缓存这样的委托是不值得的,因为这是一个非常快速的操作。

从头开始重建它并不是特别昂贵,实际上,您只是创建了一个新对象,它有两个字段,一个对象引用和一个
MethodInfo
实例(或一些等效对象)

几乎可以肯定的是,自己缓存这样的委托是不值得的,因为这是一个非常快速的操作

每次调用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);
}
所有“FullRun”数字都是运行该方法一千万次所需的毫秒数

所以你可以看到我的基准框架需要50毫秒,即使我们什么都不做。简单地调用
DoSomething
方法,假设它什么也不做,需要另外50毫秒才能运行1000万次。然后,调用
ConsumesDelegate
,将
DoSomething
作为方法组传递给它,需要额外的200毫秒或更短的时间(也需要1000万次)

如果您编写的代码将在性能关键路径中被调用数百万次,您将需要考虑完全避免lambdas。它们增加了一点点内存开销和一点点额外的CPU时间。否则,我不会费心避免方法组转换

每次调用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);
}
所有“FullRun”数字都是运行该方法一千万次所需的毫秒数

所以你可以看到我的基准框架需要50毫秒,即使我们什么都不做。简单地调用
DoSomething
方法,假设它什么也不做,需要另外50毫秒才能运行1000万次。然后,调用
ConsumesDelegate
,将
DoSomething
作为方法组传递给它,需要额外的200毫秒或更短的时间(也需要1000万次)


如果您编写的代码将在性能关键路径中被调用数百万次,您将需要考虑完全避免lambdas。它们增加了一点点内存开销和一点点额外的CPU时间。否则,我不会费心避免方法组转换。

不,它不能缓存委托。不幸的是,C#5规范(第6.6节)对此非常精确,包括在执行时行为中:“分配了一个新的委托类型D实例。”我觉得这很令人讨厌:(如果有人和我一样对Jon Skeet的评论感到困惑,那么未经编辑的此响应引用了(错误的)缓存在函数调用中隐式创建的委托的选择是一个实现细节。我认为您在编辑中引入了一个输入错误:您的意思是“不是特别昂贵”?@StriplingWarrior谢谢。不,它不能缓存委托。C#5规范(第6.6节)不幸的是,这一点非常精确,包括在执行时行为中:“分配了一个新的委托类型D实例。”我觉得这令人厌烦:(如果有人和我一样对Jon Skeet的评论感到困惑,那么此响应的未编辑版本会引用(错误的)缓存在函数调用中隐式创建的委托的选择是一个实现细节。我认为您在编辑中引入了一个输入错误:您的意思是“不是特别昂贵”?@StriplingWarrior,谢谢。
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