这个LINQ性能的优势是从哪里来的?

4

我写了一个函数来递归查找符合条件的第一个或默认项(第一个代码块)。

Resharper建议我将几行代码更改为仅一个LINQ行(第二个代码块)。

我想知道Resharper的建议是否会给我相同的性能和相同的内存占用。我进行了性能测试(第三个代码块)。结果正是我所期望的。为什么差异如此之大?

8156 milliseconds
Laure
23567 milliseconds
Laure LINQ

这种差异是从哪里来的?为什么结果不同?或者至少更接近?
public static T RecursiveFirstOrDefault<T>(this T item, Func<T, IEnumerable<T>> childrenSelector, Predicate<T> condition)
    where T : class // Hierarchy implies class. Don't need to play with "default()" here.
{
    if (item == null)
    {
        return null;
    }

    if (condition(item))
    {
        return item;
    }

    foreach (T child in childrenSelector(item))
    {
        T result = child.RecursiveFirstOrDefault(childrenSelector, condition);
        if (result != null)
        {
            return result;
        }
    }

    return null;
}

但是Resharper建议我将foreach块转换为以下LINQ查询:
public static T RecursiveFirstOrDefaultLinq<T>(this T item, Func<T, IEnumerable<T>> childrenSelector, Predicate<T> condition)
    where T : class // Hierarchy implies class. Don't need to play with "default()" here.
{
    if (item == null)
    {
        return null;
    }

    if (condition(item))
    {
        return item;
    }

    // Resharper change:
    return childrenSelector(item).Select(child => child.RecursiveFirstOrDefaultLinq(childrenSelector, condition)).FirstOrDefault(result => result != null);
}

测试:

private void ButtonTest_OnClick(object sender, RoutedEventArgs e)
{
    VariationSet varSetResult;
    Stopwatch watch = new Stopwatch();

    varSetResult = null;
    watch.Start();
    for(int n = 0; n < 10000000; n++)
    {
        varSetResult = Model.VariationRef.VariationSet.RecursiveFirstOrDefault((varSet) => varSet.VariationSets,
            (varSet) => varSet.Name.Contains("Laure"));
    }
    watch.Stop();
    Console.WriteLine(watch.ElapsedMilliseconds.ToString() + " milliseconds");
    Console.WriteLine(varSetResult.Name);

    watch.Reset();

    varSetResult = null;
    watch.Start();
    for(int n = 0; n < 10000000; n++)
    {
        varSetResult = Model.VariationRef.VariationSet.RecursiveFirstOrDefaultLinq((varSet) => varSet.VariationSets,
            (varSet) => varSet.Name.Contains("Laure"));
    }
    watch.Stop();
    Console.WriteLine(watch.ElapsedMilliseconds.ToString() + " milliseconds");
    Console.WriteLine(varSetResult.Name + " LINQ");

}

我今天要走了……希望能够适当地回答有关测试的问题:x86,Release模式在12核机器上,Windows 7,.Net Framework 4.5。

我的结论:

在我的情况下,非LINQ版本大约快3倍。虽然使用LINQ更易读,但是在库中,你只需要记住它做了什么以及如何调用它(在这种情况下,不是绝对通用的情况),所以谁关心可读性呢?LINQ几乎总比良好编码的方法慢。 我个人的喜好:

  • LINQ:在特定项目代码中性能并不是真正的问题时(大多数情况下)
  • 非LINQ:在特定项目代码中性能是一个问题时,或者在库中使用时,在使用该方法时应该有稳定的、固定的代码,并且其使用方法应该得到充分的文档说明,我们不应该真正需要深入其中。

7
你不需要重置计时器来获得第二次测试迭代的正确计数吗? - entropic
@entropic,现在更好了...我在非Linq方面有更好的性能,这正是我所期望的。我正在修改我的问题... - Eric Ouellet
IEnumerable<T>.FirstOrDefault可以带一个谓词。例如,"abcd".FirstOrDefault(f => f > 'b')返回'c'。 - user2023861
1
参见:https://dev59.com/ZWUp5IYBdhLWcg3w-Lez - Magnus
2
不要试图混合大量操作,而应该保持关注点的分离。创建一个遍历基于树形结构并将其展平的方法,您已经有了一个过滤序列以匹配条件项的方法,以及一个获取序列中第一个项或默认值的方法。如果您真的需要,那么可以组合这三个操作,尽管一旦构建了每个构建块,它并没有添加太多内容。 - Servy
显示剩余6条评论
1个回答

3
以下是与非LINQ和LINQ代码性能差异有关的一些原因:
  1. 每次调用方法都会有一些性能开销。必须将信息推送到堆栈上,CPU必须跳转到不同的指令行等。在LINQ版本中,您正在调用Select和FirstOrDefault,而在非LINQ版本中没有这样做。
  2. 创建一个Func<>以传递到Select方法时,存在时间和内存开销。当您在基准测试中多次重复此操作时,内存开销可能会导致需要更频繁地运行垃圾收集器,这可能会很慢。
  3. 您调用的Select LINQ方法生成表示其返回值的对象。这也增加了一些内存消耗。

为什么差异如此之大?

实际上并不是很大。确实,LINQ需要更长的时间,但老实说,你只能在毫秒内完成400次整个递归操作。这并不慢,除非这是您在高性能应用程序(如视频游戏)中一直在执行的操作,否则您不太可能注意到差异。

首先...非常感谢 :-) 关于“1.”,我同意它有影响,但我怀疑它不是时间的主要部分。关于“2”,我不确定是否理解正确,因为在我看来,两种情况都是相同的。关于“3”,我认为你可能指出了问题所在,因为需要创建一个新对象,在这种情况下对整个代码来说非常重要,特别是考虑到每个(在我的测试用例中)层次结构对象都有一个非常快速的条件需要验证。非常感谢! - Eric Ouellet
我做了另一个修正,没有人真正看到它而且是一个巨大的 bug... 在我的非 LINQ 版本中,我正在调用 LINQ 版本。结果也得到了更新,现在使用非 LINQ 时速度快了近三倍,这与我在 Magnus 的评论中阅读到的相同。 - Eric Ouellet
关于#2:每次调用“.Select()”时,您都会传递一个委托(child => child.RecursiveFirstOrDefaultLinq(childrenSelector, condition)),它会关闭两个变量。即使您从未说过“new”,该委托也会自动实例化,基本上与您在类上有两个字段的情况相同。因此,创建对象以及以后必须进行垃圾收集将对性能产生影响。 - StriplingWarrior
你是完全正确的。我错过了那个。这应该会产生非常大的影响。非常感谢你指出了那个事实 :-)! - Eric Ouellet

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