7

パフォーマンス プロファイラーを使用して、今日取り組んでいる Web アプリケーションのセクションを調べます。ユニオンが遅延を引き起こしていると思っていましたが、代わりに別の驚くべき結果が見つかりました。

速度低下の原因の 1 つは、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完了するまでに新しい方法の 2 倍の時間がかかることがわかりました。これは見ていてショックでした。

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; }
    }
}
4

1 に答える 1

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 プロファイラーなどを使用して舞台裏で発生しているデータベース クエリをチェックすることで証明できます。

于 2013-04-18T20:28:01.790 に答える