2013-04-18 78 views
6

查看我今天使用性能分析器處理的一段web應用程序。我認爲一個聯盟造成了一些延誤,但是卻發現了其他令人驚訝的結果。FirstOrDefault的性能()

放緩原因之一似乎是FirstOrDefault。

這是一個非常簡單的LINQ查詢是這樣的:

foreach(Report r in reports) 
    IDTOStudy study = studies.FirstOrDefault(s => s.StudyID == r.StudyID); 

我創建了一個小方法來複制我想通FirstOrDefault在做的行爲。

private IDTOStudy GetMatchingStudy(Report report, IList<IDTOStudy> studies) 
{ 
    foreach (var study in studies) 
    if (study.StudyID == report.StudyID) 
     return study; 

    return null; 
} 

這種方法取代了FirstOrDefault看起來像這樣:

foreach(Report r in reports) 
    IDTOStudy study = GetMatchingStudy(r, studies); 

望着與性能分析器運行新的代碼顯示FirstOrDefault拿兩倍的時間來完成我的新方法。這令人震驚。

我必須在FirstOrDefault()查詢中做一些不正確的事情。它是什麼?

是否FirstOrDefault()完成整個查詢,然後取第一個元素?

如何加快速度並使用FirstOrDefault()

編輯1:

一個附加一點我注意到的是,剖析說,我即將用盡我的CPU在這兩種實現的。這也是我不關心也沒有想到的。我添加的額外方法並沒有減少這個峯值,只是將其持續時間減半。

編輯3:

把學習到字典中得到了大幅提升了運行時間。這絕對是代碼的外觀。雖然不回答FirstOrDefault的問題。

編輯2:

下面是一個簡單的控制檯應用程序要求的示例代碼。我的跑步仍然表明,在大多數情況下,FirstOrDefault需要更長的時間。

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Reactive.Linq; 
using System.Reactive.Concurrency; 
using System.Diagnostics; 

namespace TestCode 
{ 
    public class Program 
    { 
     public List<IntHolder> list; 

     public static void Main(string[] args) 
     { 
      var prog = new Program(); 
      prog.list = new List<IntHolder>(); 

      prog.Add50000Items(); 
      prog.list.Add(new IntHolder() { Num = 12345 }); 
      prog.Add50000Items(); 

      var stopwatch = new Stopwatch(); 
      stopwatch.Start(); 
      prog.list.FirstOrDefault(n => n.Num == 12345); 
      stopwatch.Stop(); 

      Console.WriteLine("First run took: " + stopwatch.ElapsedTicks); 
      var lookingFor = new IntHolder() { Num = 12345 }; 

      stopwatch.Reset(); 
      stopwatch.Start(); 
      prog.GetMatching(lookingFor); 
      stopwatch.Stop(); 
      Console.WriteLine("Second run took: " + stopwatch.ElapsedTicks); 
      Console.ReadLine(); 
     } 

     public void Add50000Items() 
     { 
      var rand = new Random(); 

      for (int i = 0; i < 50000; i++) 
       list.Add(new IntHolder() { Num = rand.Next(100000) }); 
     } 

     public IntHolder GetMatching(IntHolder num) 
     { 
      foreach (var number in list) 
       if (number.Num == num.Num) 
        return number; 

      return null; 
     } 
    } 

    public class IntHolder 
    { 
     public int Num { get; set; } 
    } 
} 
+0

如果您正在使用LINQ to SQL或EF,然後'FirstOrDefault()'應該簡單地生成'TOP 1'查詢 –

+0

什麼是研究,它是一個orm對象(例如來自Entity Framework或類似的dbset)? –

+0

@lazyberezovsky我懷疑是第一個例子生成O(n)和第二個O(1)db查詢 –

回答

3

什麼,我認爲正在發生的事情(雖然這將是很好得到您的具體情況一點點額外的信息,我的假設是,這是基於你的DTO班DB場景)如下:

foreach(Report r in reports) 
    IDTOStudy study = studies.FirstOrDefault(s => s.StudyID == r.StudyID); //Database query happens here for each report 


//The whole studies table is loaded into memory which means you only do one DB query and the actual firstordefault stuff is done in memory which is quicker than going over the network 
private IDTOStudy GetMatchingStudy(Report report, IList<IDTOStudy> studies) 
{ 
    foreach (var study in studies) 
    if (study.StudyID == report.StudyID) 
     return study; 

    return null; 
} 

這是什麼意思是,在第二個例子中,你已經優化了數據庫往返(這是一個好主意)。

可以證明這個理論被檢查出數據庫查詢發生在幕後的東西,如SQL事件探查器

+3

關於這種優化的一個注意事項 - 如果您有一百萬個數據庫研究,那麼將所有內容加載到內存中都不是最佳優化 –

+0

DTO研究列表已填充SQL查詢,然後已被強制爲使用ToList()列出。所以我不認爲我在衝擊DB - 這也是我最初的想法。並且爲了澄清,列表中有大約2萬項研究。 – Chris

+3

@lazyberezovsky是絕對地,對這個問題的更好的解決方案(這將很好地規模)將做這整個事情數據庫端而不是應用程序端 –