Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# lambda allocation and collection

I saw this question today about some performance difference regarding ConcurrentDictionary methods, and I saw it as a premature micro-optimization.

However, upon some thought, I realized (if I am not mistaken), that each time we pass a lambda to a method, CLR needs to allocate the memory, pass the appropriate closure (if needed), and then collect it some time later.

There are three possibilities:

  1. Lambda without a closure:

    // 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 with a closure:

    // this is definitely an allocation
    return concurrent_dict.GetOrAdd(key, k => ValueFactory(k, stuff));
    
  3. Outside check (like checking the condition before the lock):

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

Third case is obviously allocation-free, the second one will need an allocation.

But is the first case (lambda which doesn't have a capture) completely allocation-free (at least in newer CLR versions)? Also, is this an implementation detail of the runtime, or something specified by the standard?

like image 395
Lou Avatar asked Aug 03 '16 11:08

Lou


2 Answers

First of all the CLR does not know what a lambda is. This is a C# concept. It is compiled away. The C# language provides you a delegate value where you wrote the lambda.

C# does not guarantee that the delegate instance (or underlying method) is shared or not. In fact I believe the initialization of shared lambda delegates is thread unsafe and racy. So depending on timing you might see just one or multiple delegate instances.

So it's an implementation detail of the language.

In practice you can rely on forms 1 and 3 being shared. This is important for performance. If this ever was not the case I think it would be considered a high priority bug.

like image 196
usr Avatar answered Oct 18 '22 06:10

usr


If by allocation you mean to the generated DisplayClass, so the first case will be allocation free. But it still need some allocation, the Func<Key, Value> for example.

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");
    }
}

If you want to check what happening in each case, (static\instance field, locals, this, shared generated objects)

take a look at C# specification, Section 6.5.3 Implementation example of Anonymous functions

like image 45
Dudi Keleti Avatar answered Oct 18 '22 05:10

Dudi Keleti