Guardando una sezione della webapp su cui lavoro oggi con un profiler delle prestazioni. Pensavo che un'Unione causasse alcuni ritardi, ma invece ho trovato altri risultati sorprendenti.Prestazioni di FirstOrDefault()
Una delle cause del rallentamento sembrava essere FirstOrDefault.
E 'stata una semplice query LINQ che si presentava così:
foreach(Report r in reports)
IDTOStudy study = studies.FirstOrDefault(s => s.StudyID == r.StudyID);
ho creato un piccolo metodo per riprodurre il comportamento ho pensato FirstOrDefault stava facendo.
private IDTOStudy GetMatchingStudy(Report report, IList<IDTOStudy> studies)
{
foreach (var study in studies)
if (study.StudyID == report.StudyID)
return study;
return null;
}
Questo metodo ha sostituito il FirstOrDefault a guardare come questo:
foreach(Report r in reports)
IDTOStudy study = GetMatchingStudy(r, studies);
Guardando il nuovo codice in esecuzione con il profiler prestazioni ha mostrato FirstOrDefault
di prendere il doppio del tempo per completare il mio nuovo metodo. Questo è stato uno shock da vedere.
Devo fare qualcosa di sbagliato con la query FirstOrDefault()
. Che cos'è?
FirstOrDefault()
completa l'intera query e quindi prende il primo elemento?
Come è possibile velocizzare e utilizzare FirstOrDefault()
?
Edit 1:
Un ulteriore punto ho notato è che il profiler dice che sto maxing la mia CPU su entrambi questi implementazioni. Anche questo è qualcosa che non mi interessa e che non mi aspettavo. Il metodo aggiuntivo che ho aggiunto non ha ridotto il picco, ma ne ha ridotto la durata a metà.
Edit 3:
Mettere gli studi in un dizionario ha migliorato il tempo di esecuzione tremendamente. Sarà sicuramente come appare il codice impegnato. Tuttavia, non risponde alla domanda su FirstOrDefault.
Edit 2:
Ecco il codice di esempio richiesto in una semplice console app. La mia esecuzione mostra ancora che nella maggior parte dei casi FirstOrDefault richiede più tempo.
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; }
}
}
Se si utilizza LINQ to SQL o EF, allora 'FirstOrDefault()' dovrebbe semplicemente generare 'TOP 1' query –
che cosa sono gli studi, si tratta di un oggetto orm (ad esempio un dbset da Entity Framework o simile)? –
@lazyberezovsky il mio sospetto è che il primo esempio generi O (n) e il secondo O (1) db query –