C# lambda 分配和收集

C# lambda allocation and collection

我今天看到 关于 ConcurrentDictionary 方法的一些性能差异,我认为这是过早的微优化。

然而,经过一番思考,我意识到(如果我没记错的话),每次我们将 lambda 传递给方法时,CLR 都需要分配内存,传递适当的闭包(如果需要),然后过段时间再收吧。

三种可能:

  1. 没有闭包的 Lambda:

    // the lambda should internally compile to a static method, 
    // but will CLR instantiate a new ManagedDelegate wrapper or
    // something like that?
    return concurrent_dict.GetOrAdd(key, k => ValueFactory(k));
    
  2. 带闭包的 Lambda:

    // this is definitely an allocation
    return concurrent_dict.GetOrAdd(key, k => ValueFactory(k, stuff));
    
  3. 外部检查(如锁定前检查条件):

    // no lambdas in the hot path
    if (!concurrent_dict.TryGetValue(key, out value))
        return concurrent_dict.GetOrAdd(key, k => ValueFactory(k));
    

第三种情况显然是免分配的,第二种则需要分配。

但是第一种情况(没有捕获的 lambda)是否完全没有分配(至少在较新的 CLR 版本中)?另外,这是运行时的实现细节,还是标准规定的?

如果分配是指生成的 DisplayClass,那么第一种情况将是免费分配。但它仍然需要一些分配,例如Func<Key, Value>

6.5.3 Implementation example

public delegate void D();

The simplest form of an anonymous function is one that captures no outer variables:

class Test
{
    static void F() {
        D d = () => { Console.WriteLine("test"); };
    }
}

This can be translated to a delegate instantiation that references a compiler generated static method in which the code of the anonymous function is placed:

class Test
{
    static void F() {
        D d = new D(__Method1);
    }
    static void __Method1() {
        Console.WriteLine("test");
    }
}

如果您想检查每种情况下发生了什么,(static\instance 字段、本地、this、共享生成的对象)

看看 C# specification6.5.3 匿名函数的实现示例

首先,CLR 不知道 lambda 是什么。这是一个 C# 概念。它被编译掉了。 C# 语言为您提供了一个委托值,您可以在其中编写 lambda。

C# 不保证委托实例(或底层方法)是否共享。事实上,我相信共享 lambda 委托的初始化是线程不安全和活泼的。因此,根据时间安排,您可能只会看到一个或多个委托实例。

所以这是语言的实现细节。

实际上,您可以依赖共享的表格 1 和 3。这对性能很重要。如果不是这种情况,我认为它会被视为高优先级错误。