C#lambda分配和收集

时间:2016-08-03 11:27:48

标签: .net lambda garbage-collection clr micro-optimization

我今天看到this question关于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));
    
  4. 第三种情况显然是免费分配,第二种情况需要分配。

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

2 个答案:

答案 0 :(得分:3)

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

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

所以它是该语言的实现细节。

在实践中,您可以依赖共享的表单1和表单3。这对性能很重要。如果不是这种情况,我认为它将被视为一个高优先级的错误。

答案 1 :(得分:0)

如果通过分配来表示生成的public class ThreeSum { public static int count(int[] a) { // Count triples that sum to 0. int N = a.length; int cnt = 0; for (int i = 0; i < N; i++) for (int j = i+1; j < N; j++) for (int k = j+1; k < N; k++) if (a[i] + a[j] + a[k] == 0) cnt++; return cnt; } public static void main(String[] args) { int[] a = In.readInts(args[0]); // error here StdOut.println(count(a)); // and here } } ,那么第一种情况将是免费分配。但它仍然需要一些分配,例如In.readInts

  

6.5.3实施例

DisplayClass
     

匿名函数的最简单形式是不捕获外部变量的函数:

Func<Key, Value>
     

这可以转换为委托实例化,该实例化引用编译器生成的静态方法,其中放置匿名函数的代码:

public delegate void D();

如果你想检查每种情况下发生的事情,(静态\实例字段,本地人,这个,共享生成的对象)

查看C# specification,匿名函数的 6.5.3实现示例