This question already has answers here:
foreach + break vs linq FirstOrDefault performance difference

(3个答案)


已关闭8年。




看一下我今天使用性能分析器处理的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; }
    }
}

最佳答案

我认为正在发生的事情(尽管最好在您的特定方案上获得一些额外的信息,但我假设这是基于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之类的方法检查后台发生的数据库查询来证明这一理论。

关于c# - FirstOrDefault()的性能,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/16092263/

10-17 01:01