【问题标题】:Performance of assigning a simple lambda expression or a local function to a delegate将简单的 lambda 表达式或本地函数分配给委托的性能
【发布时间】:2018-10-28 18:14:41
【问题描述】:

当使用非常简单的表达式作为键来创建带有Enumerable.ToLookup<TSource, TKey> Method (IEnumerable<TSource>, Func<TSource, TKey>) 的 ILookup 时,我可以使用 lambda 表达式:

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

或本地函数:

var lk = myItems.ToLookup(ByName);

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

我很好奇这个简单的案例是否有区别。

his answerLocal function vs Lambda C# 7.0 中,SO 用户 svick 给出了一个很好的论据,为什么 - 通常 - 局部函数比 lambdas 更可取。

很重要的一点是性能上的差异:

创建 lambda 时,必须创建委托,在这种情况下这是不必要的分配。本地函数实际上只是函数,不需要委托。

但是由于我们将它传递给ToLookup(),因此无论如何都会创建一个委托。性能上还有区别吗?
我可以想象编译器必须为 myItems.ToLookup 的每次调用创建一个新的委托 lambda,而本地方法只需要一个委托实例;这是真的吗?

svick'sanswer 在性能上的第二个不同点是变量的捕获和闭包的创建:

此外,局部函数在捕获局部变量方面效率更高:lambdas 通常将变量捕获到一个类中,而局部函数可以使用结构(使用 ref 传递),这再次避免了分配。

但是,由于表达式不使用外部范围的变量,因此 Reed CopseystatedEric LippertexpandedAre Lambda expressions in C# closures? 的回答不必有一个闭包:

可以使用闭包来实现 lambda,但它本身不一定是闭包。 — Reed Copsey
[...]
可以被视为对象的函数只是一个委托。使 lambda 成为闭包的原因在于它捕获了它的外部变量。 — Eric Lippert

这有点矛盾 Eric Lippert 自己是他的 answerAssigning local functions to delegates Eric Lippert 将本地函数解释为命名 lambda:

本地函数基本上只是一个带有关联名称的 lambda。

但这只是技术细节较少的级别,并且对于确实捕获外部范围变量的 lambda 的/本地函数的委托。

这个简单的表达式不是递归的,不是泛型的,也不是迭代器。哪个更好看是一个见仁见智的问题。
那么,简单的非捕获、非递归、非泛型和非迭代器 lambda 表达式与本地函数在性能(或其他方面)是否存在差异?

【问题讨论】:

  • 请参阅 John Skeet stackoverflow.com/q/49299443/2499773的这个答案
  • 我在最后引用的答案中的意图只是说,如果有办法以 lambda 的形式创建函数,那么还有一种方法可以创建逻辑上相同的局部函数。我并不打算暗示实施细节是相同的。抱歉,如果这令人困惑。我已经更新了链接答案中的文字,希望能更清楚。

标签: c# lambda c#-7.0


【解决方案1】:

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

the IL of code that has your two samples in separate methods,效果如下:

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 在这种特定情况下更加高效。

虽然有a proposal on GitHub to change the compiler to make LocalFunction as efficient as Lambda

【讨论】:

    猜你喜欢
    • 2021-10-18
    • 1970-01-01
    • 1970-01-01
    • 2022-10-05
    • 1970-01-01
    • 2011-08-14
    • 2010-10-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多