7

查看我今天使用性能分析器处理的 web 应用程序的一部分。我认为联合导致了一些延迟,但发现了其他令人惊讶的结果。

放缓的原因之一似乎是 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; }
    }
}
4

1 回答 1

3

我认为正在发生的事情(尽管获得有关您的特定场景的一些额外信息会很好,但我的假设是这是基于您的 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;
}

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

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

于 2013-04-18T20:28:01.790 回答