将简单的lambda表达式或本地函数分配给委托的性能

7

当使用非常简单的表达式作为键来创建一个ILookup时,可以使用lambda表达式,具体方法请参考Enumerable.ToLookup<TSource, TKey> Method (IEnumerable<TSource>, Func<TSource, TKey>)

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

或者是一个本地函数:
var lk = myItems.ToLookup(ByName);

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

我很好奇在这个简单的情况下是否有区别。
Local function vs Lambda C# 7.0中,SO用户svick他的回答中提出了一个好的论点,即通常情况下本地函数比lambda更可取。
重要的一点是性能上的差异:
创建lambda时,必须创建委托,在这种情况下,这是不必要的分配。本地函数只是函数,不需要委托。
但是因为我们将它传递给ToLookup(),所以仍然会创建委托。性能上还有区别吗? 我可以想象编译器必须为每次调用myItems.ToLookup创建一个新的委托lambda,而对于本地方法只需要一个委托实例;这是真的吗?
svick答案中,性能的第二个不同点是变量的捕获和闭包的创建:
此外,本地函数在捕获本地变量方面更加高效:lambda通常将变量捕获到类中,而本地函数可以使用一个结构体(通过ref传递),这样可以避免分配。
然而,由于表达式没有使用外部作用域的变量,因此不必像Reed Copsey所述,并且Eric Lippert在回答C#中的Lambda表达式是否为闭包?时进行了扩展
一个lambda可能是使用闭包实现的,但本身并不一定是闭包。 — Reed Copsey
[...]
一个可以被视为对象的函数只是一个委托。使lambda成为闭包的是它捕获了外部变量。 — Eric Lippert
这在某种程度上与Eric Lippert自己在答案将局部函数分配给委托中所说的相矛盾。Eric Lippert将局部函数解释为一种命名lambda:
局部函数基本上只是一个带有关联名称的lambda。
但这在技术细节方面比较次要,仅适用于捕获外部作用域变量的lambda/local functions的委托。

这个简单的表达式不是递归的,也不是通用的,也不是迭代器。哪一个更好看是一个主观看法。
那么,在简单的不捕获、非递归、非通用和非迭代器的lambda表达式和本地函数之间有什么性能(或其他方面)上的区别吗?


1
请参考John Skeet在https://dev59.com/pFUM5IYBdhLWcg3wINWk的回答。 - Xiaoguo Ge
我在上一个引用的回答中的意图只是想说,如果有一种方法可以将函数形式化为lambda,则也有一种方法可以制作逻辑上相同的本地函数。我并不打算暗示实现细节是相同的。如果造成了困惑,请接受我的道歉。我已经更新了链接答案中的文本,希望更加清晰明了。 - Eric Lippert
1个回答

7

使用当前版本的编译器(Roslyn 2.8.0),带有lambda表达式的版本更加高效,因为它缓存了委托。

查看具有您两个样本的代码在单独方法中的IL,其实现如下:

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

请注意,Lambda 只分配一次委托,并在以后使用缓存的委托,而 LocalFunction 每次都会分配委托。这使得在这种特定情况下 Lambda 更高效。
尽管有一个 在 GitHub 上的提案,试图改变编译器使 LocalFunctionLambda 一样高效。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接