代码之家  ›  专栏  ›  技术社区  ›  Kasper van den Berg

将简单lambda表达式或局部函数赋给委托的性能

  •  5
  • Kasper van den Berg  · 技术社区  · 7 年前

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

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

    或局部功能:

    var lk = myItems.ToLookup(ByName);
    
    string ByName(MyClass x)
    {
         return x.Name;
    }
    

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

    his answer Local function vs Lambda C# 7.0 销售订单用户 svick 给出了一个很好的理由,为什么在一般情况下局部函数比lambda更可取。

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

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

    但自从我们把它传给 ToLookup() 仍将创建代理。性能上还有区别吗?
    我可以想象编译器必须为每次调用myitems.tolookup创建一个新的委托lambda,而本地方法只需要一个委托实例;这是真的吗?

    第二个性能差异点 svick's answer 捕获变量和创建闭包:

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

    但是,由于表达式不使用来自外部作用域的变量,因此不必将闭包作为 stated 通过 Reed Copsey expanded 通过 Eric Lippert 作为对 Are Lambda expressions in C# closures? :

    lambda可以使用闭包实现,但它本身并不一定是闭包。艾斯 里德·科普西
    […]
    可以被视为对象的函数只是一个委托。使lambda成为闭包的原因是它捕获其外部变量。 利珀特

    这有点矛盾 利珀特 他自己就是他的 answer Assigning local functions to delegates 利珀特 将本地函数解释为命名lambda:

    局部函数基本上只是一个具有关联名称的lambda。

    但这是一个技术细节较少的层次,对于lambda的/本地功能的代表来说, 捕获外部范围变量。

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

    1 回复  |  直到 7 年前
        1
  •  5
  •   svick Raja Nadar    7 年前

    对于当前版本的编译器(Roslyn2.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 每次分配委托。这使得 兰姆达 在这种特定情况下效率更高。

    尽管有 a proposal on GitHub to change the compiler to make LocalFunction as efficient as Lambda .