FirstOrDefault的性能()

Chr*_*ris 6 .net c# linq

查看我今天使用性能分析器处理的webapp的一部分.我认为联盟正在造成一些延误,但却找到了其他令人惊讶的结果.

减速的原因之一似乎是FirstOrDefault.

这是一个非常简单的LINQ查询,如下所示:

foreach(Report r in reports)
    IDTOStudy study = studies.FirstOrDefault(s => s.StudyID == r.StudyID);
Run Code Online (Sandbox Code Playgroud)

我创建了一个小方法来复制我认为FirstOrDefault正在做的行为.

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

    return null;
}
Run Code Online (Sandbox Code Playgroud)

此方法将FirstOrDefault替换为如下所示:

foreach(Report r in reports)
    IDTOStudy study = GetMatchingStudy(r, studies);
Run Code Online (Sandbox Code Playgroud)

查看使用性能分析器运行的新代码显示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; }
    }
}
Run Code Online (Sandbox Code Playgroud)

Not*_*ple 4

我认为正在发生的事情(虽然最好获得有关您的特定场景的一些额外信息,但我的假设是这是基于您的 DTO 类的数据库场景)如下:

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;
}
Run Code Online (Sandbox Code Playgroud)

这意味着在第二个示例中您已经针对数据库往返进行了优化(这是一个好主意)。

您可以通过使用 SQL Profiler 之类的工具检查幕后发生的数据库查询来证明这一理论

  • 关于这种优化的一个注意事项 - 如果数据库中有 100 万个研究,那么将所有研究加载到内存中并不是一个好的优化 (4认同)
  • @lazyberezovsky 是的,绝对是,解决这个问题的更好的解决方案(可以很好地扩展)是在数据库端而不是应用程序端完成这一切 (3认同)