2014-09-03 66 views
4

我做了一个递归查找符合条件的第一个或默认项目(第一个代码块)的函数。此LINQ性能来自哪里?

Resharper建议我只在一条LINQ行(第二个代码块)中更改几行。

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

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几乎总是比好的编码方法慢。 我会personnaly味:

  • LINQ:如果性能是不是真的在 具体项目代码的问题(大多数情况下)
  • 非LINQ的:在性能是具体项目的代码的问题,其中使用一个库和代码应该是稳定和固定的方法的使用应该有据可查,我们 不应该真的需要挖掘内部。
+0

底层编译器编译的linq代码和foreach代码有很大不同,这就是你的区别所在。 – 2014-09-03 18:05:07

+7

您是否必须重置计时器以获得第二次测试迭代的正确计数? – entropic 2014-09-03 18:05:41

+0

@entropic,谢谢... :-(!!!!! – 2014-09-03 18:06:41

回答

3

这里有一些原因有非LINQ和LINQ代码性能之间的差异:

  1. 每次调用一个方法具有一定的性能开销。必须将信息压入堆栈,CPU必须跳转到不同的指令行等。在LINQ版本中,您将调用Select和FirstOrDefault,而您在非LINQ版本中没有这样做。
  2. 当您创建一个Func<>传入Select方法时,会有开销,包括时间和内存。内存开销如果与基准测试相比倍增很多倍,则可能导致需要更频繁地运行垃圾回收器,这可能会很慢。
  3. 您正在调用的Select LINQ方法会生成一个表示其返回值的对象。这也增加了一点内存消耗。

为什么差别那么大?

实际上并不是那么大。当然,LINQ需要花费50%的时间,但老实说你只能在毫秒中完成整个递归操作400次。这并不慢,除非这是一个在视频游戏等高性能应用程序中始终处于运行状态的操作,否则您不可能注意到其中的差异。

+0

首先...非常感谢:-)。 关于“1.”,我同意它有影响,我怀疑它不是当时的大部分。关于“2”,我不确定要理解,因为就我所见,两种情况都是一样的。关于“3”,我认为你可能把它放在它上面,因为它需要创建一个新的对象,在这种情况下,对于整个代码来说是重要的,同时考虑到每个(在我的测试用例中)层次结构对象作为一个非常快速的条件来验证。十分感谢! – 2014-09-04 13:37:59

+0

我做了另一个修正,没有人真的看到,是一个巨大的bug ...在我的非LINQ版本中,我叫Linq版本。结果也被更新了,现在它几乎是不使用LINQ的3倍,这与我在我的问题的评论中的Magnus链接中的红色相同。 – 2014-09-04 13:51:29

+0

@EricOuellet:关于#2:每次调用'.Select()'时,都会传递一个委托('child => child.RecursiveFirstOrDefaultLinq(childrenSelector,condition)'),它关闭两个变量。即使你从不说“新”,那个委托会自动实例化,基本上就像你有一个有两个字段的类一样。因此,创建一个对象以及稍后垃圾收集该对象的必要性将对性能产生影响。 – StriplingWarrior 2014-09-04 16:02:41