Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performance of assigning a simple lambda expression or a local function to a delegate

Tags:

c#

lambda

c#-7.0

When using a very simple expression as key to create an ILookup with Enumerable.ToLookup<TSource, TKey> Method (IEnumerable<TSource>, Func<TSource, TKey>) I can use a lambda expression:

var lk = myItems.ToLookup((x) => x.Name);

or a local function:

var lk = myItems.ToLookup(ByName);

string ByName(MyClass x)
{
     return x.Name;
}

I'm curious if there is a difference in this simple case.

In his answer to Local function vs Lambda C# 7.0 SO user svick gives a good argument why –in general– local functions are preferable to lambdas.

An important point is the difference in performance:

When creating a lambda, a delegate has to be created, which is an unnecessary allocation in this case. Local functions are really just functions, no delegates are necessary.

But since we pass it to ToLookup() a delegate is created anyway. Is there still a difference in performance?
I can imagine that the compiler has to create a fresh delegate lambda for each invocation of myItems.ToLookup, whereas there only needs to be a single delegate instance for the local method; is this true?

A second point of difference in performance in svick's answer is the capturing of variables and the creation of closures:

Also, local functions are more efficient with capturing local variables: lambdas usually capture variables into a class, while local functions can use a struct (passed using ref), which again avoids an allocation.

However, since the expression does not use variables from the outer scope, there does not have to be a closure as stated by Reed Copsey and expanded by Eric Lippert in answer to Are Lambda expressions in C# closures?:

A lambda may be implemented using a closure, but it is not itself necessarily a closure. — Reed Copsey
[...]
a function that can be treated as an object is just a delegate. What makes a lambda a closure is that it captures its outer variables. — Eric Lippert

This is somewhat contradicted Eric Lippert himself is his answer to Assigning local functions to delegates Eric Lippert explains a local function as a named lambda:

A local function is basically just a lambda with an associated name.

But this is at a level of lesser technical detail and for delegates of lambda's/local functions that do capture outer scope variables.

This simple expression is not recursive, not generic, and not an iterator. And which looks better is a matter of opinion.
So, are there any differences in performance (or otherwise) between simple not capturing, non recursive, non generic, and non iterator lambda expressions and local functions?

like image 879
Kasper van den Berg Avatar asked May 18 '18 10:05

Kasper van den Berg


People also ask

How you can assign a lambda expression to a delegate?

Since a lambda expression is just another way of specifying a delegate, we should be able to rewrite the above sample to use a lambda expression instead of an anonymous delegate. In the preceding example, the lambda expression used is i => i % 2 == 0 . Again, it is just a convenient syntax for using delegates.

What is the difference between lambda expression and delegate?

The difference really is that a lambda is a terse way to define a method inside of another expression, while a delegate is an actual object type.

How are an anonymous delegate and a lambda expression related?

Anonymous Method is an inline code that can be used wherever a delegate type is expected. Microsoft introduced Anonymous Methods in C# 2.0 somewhere around 2003. Lambda expression is an anonymous method that you can use to create delegates or expression tree types.

Which of the following statement is true about lambda expression?

What is the correct statement about lambda expression? Explanation: Return type in lambda expression can be ignored in some cases as the compiler will itself figure that out but not in all cases. Lambda expression is used to define small functions, not large functions.


1 Answers

With the current version of the compiler (Roslyn 2.8.0), the version with lambda is more efficient, because it caches the delegate.

Looking at the IL of code that has your two samples in separate methods, it's effectively:

sealed class HelperClass
{
    public static readonly HelperClass Instance = new HelperClass();

    public static Func<MyClass, string> CachedDelegate;

    internal string LambdaByName(MyClass x)
    {
        return x.Name;
    }

    internal string LocalFunctionByName(MyClass x)
    {
        return x.Name;
    }
}

void Lambda(IEnumerable<MyClass> myItems)
{
    var lk = myItems.ToLookup(HelperClass.CachedDelegate ??
        (HelperClass.CachedDelegate =
            new Func<MyClass, string>(HelperClass.Instance.LambdaByName)));
}

void LocalFunction(IEnumerable<MyClass> myItems)
{
    var lk = myItems.ToLookup(
        new Func<MyClass, string>(HelperClass.Instance.LocalFunctionByName)));
}

Notice how Lambda allocates the delegate only once and uses a cached delegate afterwards, while LocalFunction allocates the delegate every time. This makes Lambda more efficient in this specific case.

Though there is a proposal on GitHub to change the compiler to make LocalFunction as efficient as Lambda.

like image 62
svick Avatar answered Sep 18 '22 12:09

svick