2013-04-18 5 views
6

webappのセクションを見ると、今日はパフォーマンスプロファイラーで作業しています。私は、連合がいくつかの遅れを引き起こしていると思ったが、代わりに他の驚くべき結果を見出した。FirstOrDefault()のパフォーマンス

減速の原因の1つがFirstOrDefaultであるように見えました。

それはこのように見えた非常に単純なLINQクエリだった:私はFirstOrDefaultをやっていた考え出し行動を複製する小さなメソッドを作成し

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

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:私が気づい

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

ようなもので舞台裏で起こってデータベースクエリをチェックアウトすることにより、この理論を証明することができ、その後、 'FirstOrDefaultは()'単に 'TOPを生成する必要があります '1'クエリ –

+0

調査は何ですか、ormオブジェクト(Entity Frameworkなどのdbset)ですか? –

+0

@ lazyberezovsky私の疑念は、最初の例ではO(n)と2番目の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; 
} 

これは、2番目の例ではデータベースのラウンドトリップを最適化したことを示しています(これは良い考えです)。あなたはSQLまたはEFへのLINQを使用している場合

あなたはSQLプロファイラ

+3

このような最適化に関する1つの注意点 - データベースに100万件の研究がある場合、すべてをメモリにロードするのは最適化ではない –

+0

DTOスタディリストにSQLクエリが設定され、ToList()で強制的にリストされています。だから私はDBを打っているとは思わない - それは私の最初の考えだった。そして明確にするために、リストには約2万の研究があります。 – Chris

+3

@lazyberezovskyええ、この問題(これはうまくスケールする)のさらに良い解決策は、アプリケーション側ではなくデータベース全体を行うことです –

関連する問題