【问题标题】:Where does this LINQ performance come from?这种 LINQ 性能从何而来?
【发布时间】:2014-10-28 08:05:44
【问题描述】:

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

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,在12核机器上发布,windows 7,Framework.Net 4.5,

我的结论:

在我的情况下,它在非 linq 版本中要快约 3 倍。 LINQ 的可读性更好,但谁在乎它何时在一个库中,您应该只记住它的作用以及如何调用它(在这种情况下 - 不是一般的绝对情况)。 LINQ 几乎总是比好的编码方法慢。 我会个人口味:

  • LINQ:性能不是真正的问题(大多数情况下) 具体项目代码
  • Non Linq:性能是特定项目代码中的问题,在库中使用的位置以及代码应该稳定和固定的位置,方法的使用应该有详细的文档,我们 不应该真的需要在里面挖。

【问题讨论】:

  • 底层编译器编译的 linq 代码与 foreach 代码截然不同,这就是你的区别所在。
  • 您不需要重置计时器以获得第二次测试迭代的正确计数吗?
  • @entropic,谢谢... :-( !!!!!
  • 与其尝试将一大堆操作混合在一起,不如保持关注点分离。创建一个遍历基于树的结构并将其展平的方法,您已经有一种方法可以将序列过滤为符合条件的项目,并且您已经有一种方法可以获取序列中的第一项或默认值。然后,如果您真的需要,您可以组合这三个操作,尽管一旦您构建了每个构建块,它并不会完全添加很多。

标签: c# performance linq memory optimization


【解决方案1】:

以下是非 LINQ 和 LINQ 代码性能存在差异的一些原因:

  1. 对方法的每次调用都会产生一些性能开销。信息必须被压入堆栈,CPU 必须跳转到不同的指令行,等等。在 LINQ 版本中,您正在调用 Select 和 FirstOrDefault,而在非 LINQ 版本中没有这样做。
  2. 当您创建Func&lt;&gt; 以传递给 Select 方法时,会产生时间和内存开销。当您在基准测试中多次乘以内存开销时,可能会导致需要更频繁地运行垃圾收集器,这可能会很慢。
  3. 您正在调用的 Select LINQ 方法会生成一个表示其返回值的对象。这也会增加一点内存消耗。

为什么差距这么大?

实际上并没有那么大。诚然,LINQ 花费的时间要长 50%,但老实说,您说的是只能在 毫秒 内完成整个递归操作 400 次。这并不慢,而且您不太可能注意到差异,除非这是您在视频游戏等高性能应用程序中一直在执行的操作。

【讨论】:

  • 首先...非常感谢:-)。关于“1.”,我同意它有影响,我怀疑这不是大部分时间。关于“2”,我不确定是否理解,因为在我看来,这两种情况都是一样的。关于“3”,我认为您可能会把手指放在它上面,因为它需要创建一个新对象,在这种情况下,这对整个代码来说很重要,同时考虑到每个(在我的测试用例中)层次结构对象作为一个非常快速的条件来验证。非常感谢!
  • 我做了另一个没有人真正看到的更正,这是一个巨大的错误......在我的非 linq 版本中,我调用的是 Linq 版本。结果也更新了,现在不使用 LINQ 时速度快了近 3 倍,这与我在问题的 cmets 中的 Magnus 链接中红色的相同。
  • @EricOuellet:关于#2:每次调用.Select() 时,都会传入一个委托(child =&gt; child.RecursiveFirstOrDefaultLinq(childrenSelector, condition)),它关闭了两个变量。即使您从未说过“new”,该委托也会自动实例化,基本上就像您有一个带有两个字段的类一样。因此,创建一个对象,再加上稍后对该对象进行垃圾收集的必要性,都会对性能产生影响。
  • 你是完全正确的。我错过了。那么它应该会产生很大的影响。非常感谢你指出我这个事实:-)!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-31
  • 2015-05-05
  • 1970-01-01
相关资源
最近更新 更多