2014-09-03 69 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