FirstOrDefault的性能()

时间:2013-04-18 20:17:04

标签: c# .net linq

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

减速的原因之一似乎是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; }
    }
}

1 个答案:

答案 0 :(得分: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

之类的幕后数据库查询来证明这一理论