C#Lambda性能问题/可能性/准则

我使用各种lambda表达式语法测试性能差异。 如果我有一个简单的方法:

public IEnumerable<Item> GetItems(int point)
{
    return this.items.Where(i => i.IsApplicableFor(point));
}

那么这里有一些与point参数相关的变量,因为它是从lambda的角度来看的一个自由变量。 如果我将这种方法调用一百万次,最好是保持原样,还是以任何方式改变它以提高性能?

我有什么选择,哪些可行? 据我了解这是我必须摆脱自由变量,所以编译器将不必创建闭包类,并在每次调用此方法时实例化它。 与非关闭版本相比,此实例通常需要大量时间。

问题是我想提出一些通常可行的lambda编写指南,因为每次我写一个严重打击的lambda表达式时,似乎都浪费了一些时间。 我必须手动对其进行测试以确保它能正常工作,因为我不知道要遵循哪些规则。

替代方法

示例控制台应用程序代码

我还写了一个不需要任何变量提升的同一个方法的不同版本(至少我认为它没有,但是你们懂得这个让我知道如果是这样的话):

public IEnumerable<Item> GetItems(int point)
{
    Func<int, Func<Item, bool>> buildPredicate = p => i => i.IsApplicableFor(p);
    return this.items.Where(buildPredicate(point));
}

在这里查看Gist。 只需创建一个控制台应用程序,并将整个代码复制到namespace块内的Program.cs文件中。 即使不使用自由变量,您将看到第二个示例速度要慢得多。

一个矛盾的例子

我之所以想要构建一些lambda最佳使用指南,是因为我之前遇到过这个问题,并且令我惊讶的是,当使用谓词构建器lambda表达式时,其结果是工作得更快。

现在解释一下。 我完全迷失在这里,因为它可能会变成我不会使用lambda,当我知道我的代码中有一些重用方法时。 但是我想避免这种情况,并深入到底。

编辑

您的建议似乎不起作用

我试过实现一个自定义查找类,它在内部工作方式与编译器用自由变量lambda类似。 但是我没有实现一个闭包类,而是实现了模拟类似场景的实例成员。 这是代码:

private int Point { get; set; }
private bool IsItemValid(Item item)
{
    return item.IsApplicableFor(this.Point);
}

public IEnumerable<TItem> GetItems(int point)
{
    this.Point = point;
    return this.items.Where(this.IsItemValid);
}

有趣的是,这个工作和慢速版本一样慢。 我不知道为什么,但似乎除了快速之外别无他法。 它重用相同的功能,因为这些额外的成员是同一个对象实例的一部分。 无论如何。 我现在非常困惑

我已经更新了Gist源代码,因此您可以自行测试。


是什么让你认为第二个版本不需要任何变量提升? 你用Lambda表达式定义了Func ,这需要第一个版本需要的编译器技巧。

此外,你要创建一个Func ,返回一个Func ,其弯曲我的大脑一点点,几乎肯定会需要重新评估每个调用。

我建议你在发布模式下编译它,然后使用ILDASM来检查生成的IL。 这应该会让您对生成的代码有所了解。

你应该运行的另一个测试,它会给你更多的见解,就是使谓词调用成为一个独立的函数,它在类作用域使用一个变量。 就像是:

private DateTime dayToCompare;
private bool LocalIsDayWithinRange(TItem i)
{
    return i.IsDayWithinRange(dayToCompare);
}

public override IEnumerable<TItem> GetDayData(DateTime day)
{
    dayToCompare = day;
    return this.items.Where(i => LocalIsDayWithinRange(i));
}

这会告诉你,如果提升day变量实际上是在花费任何东西。

是的,这需要更多的代码,我不会建议你使用它。 正如你在回答前面提到的类似的回答中所指出的那样,这将产生相当于使用局部变量的闭包。 关键是要么你或编译器必须做这样的事情才能使事情顺利进行。 除了编写纯粹的迭代解决方案外,没有什么魔法可以执行,这将防止编译器必须执行此操作。

我的观点是,在我的情况下“创建闭包”是一个简单的变量赋值。 如果这比使用Lambda表达式的版本快得多,那么您知道编译器为闭包创建的代码存在一些低效率。

我不确定你在哪里得到有关必须消除自由变量的信息,以及关闭的成本。 你能给我一些参考吗?


你的第二种方法比我的第一种方法慢8倍。 正如@DanBryant在评论中所说的,这是关于在方法内部构造和调用委托 - 而不是做可变提升。

你的问题令人困惑,因为它对我来说就像你期望第二个样本比第一个样本快。 我也读了它,因为“变量提升”,第一个以某种方式慢得令人无法接受。 第二个示例仍然有一个自由变量( point ),但它增加了额外的开销 - 我不明白为什么你会认为它删除了自由变量。

正如您发布的代码所证实的那样,上面的第一个示例(使用简单的内联谓词)比简单的for循环慢了10% - 从您的代码执行:

foreach (TItem item in this.items)
{
    if (item.IsDayWithinRange(day))
    {
        yield return item;
    }
}

总之:

  • for循环是最简单的方法,并且是“最好的情况”。
  • 由于一些额外的开销,内联谓词稍微慢一些。
  • 构建并调用一个Func返回Func每次迭代内是比任显著慢。
  • 我不认为这有什么意外。 '准则'是使用一个内联谓词 - 如果它执行得不好,通过移动到一个直线循环来简化。


    我介绍了你的基准,并确定了很多事情:

    首先,它在线上花费一半时间return this.GetDayData(day).ToList(); 调用ToList 。 如果您删除它,而是手动迭代结果,则可以测量方法中的差异。

    其次,因为IterationCount = 1000000RangeCount = 1 ,所以您正在对不同方法的初始化进行计时,而不是执行它们所需的时间。 这意味着您的执行配置文件主要是通过创建迭代器,转义变量记录和委托以及通过创建所有垃圾而产生的数百个随后的gen0垃圾回收。

    第三,x86上的“慢”方法非常慢,但与x64上的“快速”方法一样快。 我相信这是由于不同的JIT创建代表的原因。 如果您从结果中折扣委托创建,“快速”和“慢速”方法的速度相同。

    第四,如果实际上调用迭代器的次数很多(在我的计算机上,以x64为目标, RangeCount = 8 ),“slow”实际上比“foreach”更快,“fast”比所有更快。

    总之,“提升”方面可以忽略不计。 在我的笔记本电脑上测试显示,捕获像你这样的变量每次创建lambda需要额外的10ns (并非每次调用它),并且包括额外的GC开销。 此外,在“foreach”方法中创建迭代器的速度比创建lambda表达式要快,但实际调用该迭代器比调用lambda表达式要慢。

    如果创建代理所需的额外几纳秒对于您的应用程序来说太多了,请考虑缓存它们。 如果你需要这些代理的参数(例如闭包),考虑创建你自己的闭包类,以便你可以创建它们一次,然后在需要重用代表时更改属性。 这是一个例子:

    public class SuperFastLinqRangeLookup<TItem> : RangeLookupBase<TItem>
        where TItem : RangeItem
    {
    
        public SuperFastLinqRangeLookup(DateTime start, DateTime end, IEnumerable<TItem> items)
            : base(start, end, items)
        {
            // create delegate only once
            predicate = i => i.IsDayWithinRange(day);
        }
    
        DateTime day;
        Func<TItem, bool> predicate;
    
        public override IEnumerable<TItem> GetDayData(DateTime day)
        {
            this.day = day; // set captured day to correct value
            return this.items.Where(predicate);
        }
    }
    
    链接地址: http://www.djcxy.com/p/51547.html

    上一篇: C# Lambda performance issues/possibilities/guidelines

    下一篇: Encrypting powershell passwords