2010-02-14 7 views
30

Chiunque conosca eventuali differenze di velocità tra Dove e FindAll nell'elenco. So che Where fa parte di IEnumerable e FindAll è parte di List, sono solo curioso di sapere cosa è più veloce.C# FindAll VS Where Speed ​​

+0

possibile duplicato di [FindAll vs Where extension-method] (http://stackoverflow.com/questions/1531702/findall-vs-where-extension-method) –

risposta

45

Il metodo FindAll della classe List <T> crea effettivamente un nuovo oggetto elenco e aggiunge risultati. Il metodo di estensione Dove per IEnumerable <T> semplicemente iterare elenco esistente e cedere un'enumerazione dei risultati corrispondenti senza creare o aggiungere nulla (diverso dal enumeratore stessa.)

Dato un piccolo insieme, i due probabilmente sarebbe eseguire in modo comparabile. Tuttavia, dato un set più grande, Dove dovrebbe sovraperformare FindAll, poiché il nuovo Elenco creato per contenere i risultati dovrà crescere dinamicamente per contenere ulteriori risultati. L'utilizzo della memoria di FindAll inizierà anche a crescere in modo esponenziale con il crescere del numero di risultati corrispondenti, dove dovrebbe avere un utilizzo costante della memoria minimo (in sé e per sé ... escludendo qualunque cosa tu faccia con i risultati.)

+19

L'eccezione è dove si desidera effettivamente avere un elenco in seguito (forse è necessario chiamare "Conteggio" o cambiare membri, o scorrere più volte). Mentre 'Where()' batte 'FindAll()', 'FindAll()' batte 'Where(). ToList()'. –

+5

@JonHanna: Mentre inizialmente pensavo di essere d'accordo, in realtà non ne sono sicuro. Avete riferimenti che indicano che un .ToList() è più lento di un .FindAll()? Chiamare .ToList() su una query sarebbe ** essere ** l'iterazione dell'enumerabile, e quindi dovrebbe mantenere la sua efficienza di memoria. Non solo, alcune implementazioni interne del punto in cui l'iteratore potrebbe persino essere in grado di creare un elenco delle dimensioni giuste (allocazione della memoria) in anticipo, sovraperformando FindAll in questi casi. Non sono specificamente in disaccordo, tuttavia sarebbe bello avere una solida referenza che chiarisca il vantaggio di FindAlls. – jrista

+1

Questa risposta è completamente sbagliata. Vedi @Wory che si è preso la briga di misurare effettivamente. –

3

.FindAll() essere più veloce, si avvantaggia di conoscere già le dimensioni della lista e il looping attraverso la matrice interna con un semplice ciclo for. .Where() deve accendere un enumeratore (una classe di struttura sigillata denominata WhereIterator in questo caso) e svolgere lo stesso lavoro in un modo meno specifico.

Tuttavia, tenere presente che.()() È enumerabile, non crea attivamente un elenco in memoria e lo riempie. È più simile a un flusso, quindi l'utilizzo della memoria su qualcosa di molto grande può avere una differenza significativa. Inoltre, potresti iniziare a usare i risultati in modo parallelo molto più velocemente usando l'approccio .Where() in 4.0.

+1

WhereEnumerableIterator, anziché WhereIterator, viene effettivamente utilizzato a meno che non si includa l'indice nella clausola where. WhereEnumerableIterator è notevolmente più efficiente di WhereIterator. Nel caso dell'elenco , viene addebitato il costo di una chiamata di metodo aggiuntiva (che dovrebbe essere indicata nel codice di rilascio), ma non è necessario ridimensionare dinamicamente un elenco interno come parte del suo trattamento. L'efficienza di Dove dovrebbe sovraperformare FindAll in tutti gli elenchi tranne quelli più piccoli (qualsiasi valore maggiore di 4 risultati risulterà in una o più ridimensionazioni.) – jrista

+0

Nel caso di chiamare Dove su una matrice o Elenco , esistono due ulteriori classi interne di iteratore, WhereArrayIterator e WhereListIterator, che sono ottimizzati per questi due casi. In generale, chiamare Dove dovrebbe essere più efficiente di chiamare FindAll. – jrista

+2

@jrista - I ** completamente ** ha mancato lo stack del caso in '.Dove() sovraccarico restituendo quelli, grazie! Guardando attraverso quel codice sono d'accordo. Dovrebbe avere, nel peggiore dei casi, prestazioni uguali ma quasi sempre migliori. Inoltre, SO sarebbe inutile se non fosse per le persone che impiegano il tempo extra per educare gli altri, ad es. tu e questi commenti, +1 per insegnarmi qualcosa. –

4

Where è molto, molto più veloce di FindAll. Non importa quanto sia grande la lista, Where richiede esattamente lo stesso tempo.

Ovviamente Where crea solo una query. In realtà non fa nulla, a differenza di FindAll che crea un elenco.

+5

Questo può essere tecnicamente vero, ma penso che sia abbastanza chiaro che l'OP si sta interrogando sulle prestazioni nel contesto di enumerare effettivamente il risultato, non la chiamata al metodo nudo stesso. –

-3

La risposta di jrista rende sensati. Tuttavia, la nuova lista aggiunge gli stessi oggetti, quindi cresce solo con riferimento agli oggetti esistenti, che non dovrebbero essere così lenti. Fintanto che l'estensione 3.5/Linq è possibile, dove sta comunque meglio. FindAll ha molto più senso se limitato con 2.0

6

FindAll è ovviamente più lento di Dove, perché è necessario creare un nuovo elenco.

In ogni caso, penso che dovresti prendere in considerazione il commento di Jon Hanna: probabilmente dovrai eseguire alcune operazioni sui risultati e la lista sarebbe più utile di IEnumerable in molti casi.

Ho scritto un test di piccole dimensioni, è sufficiente incollarlo nel progetto App Console. Misura il tempo/tick di: esecuzione della funzione, operazioni sulla raccolta dei risultati (per ottenere il perfetto utilizzo "reale", e per essere sicuro che il compilatore non ottimizzerà i dati inutilizzati ecc. - Sono nuovo di C# e non lo faccio sapere come funziona ancora, mi dispiace).

Avviso: ogni funzione misurata tranne WhereIENumerable() crea una nuova lista di elementi. Potrei fare qualcosa di sbagliato, ma chiaramente iterando IEnumerable richiede molto più tempo dell'iterazione iteratrice.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Diagnostics; 

namespace Tests 
{ 

    public class Dummy 
    { 
     public int Val; 
     public Dummy(int val) 
     { 
      Val = val; 
     } 
    } 
    public class WhereOrFindAll 
    { 
     const int ElCount = 20000000; 
     const int FilterVal =1000; 
     const int MaxVal = 2000; 
     const bool CheckSum = true; // Checks sum of elements in list of resutls 
     static List<Dummy> list = new List<Dummy>(); 
     public delegate void FuncToTest(); 

     public static long TestTicks(FuncToTest function, string msg) 
     { 
      Stopwatch watch = new Stopwatch(); 
      watch.Start(); 
      function(); 
      watch.Stop(); 
      Console.Write("\r\n"+msg + "\t ticks: " + (watch.ElapsedTicks)); 
      return watch.ElapsedTicks; 
     } 
     static void Check(List<Dummy> list) 
     { 
      if (!CheckSum) return; 
      Stopwatch watch = new Stopwatch(); 
      watch.Start(); 

      long res=0; 
      int count = list.Count; 
      for (int i = 0; i < count; i++)  res += list[i].Val; 
      for (int i = 0; i < count; i++)  res -= (long)(list[i].Val * 0.3); 

      watch.Stop(); 
      Console.Write("\r\n\nCheck sum: " + res.ToString() + "\t iteration ticks: " + watch.ElapsedTicks); 
     } 
     static void Check(IEnumerable<Dummy> ieNumerable) 
     { 
      if (!CheckSum) return; 
      Stopwatch watch = new Stopwatch(); 
      watch.Start(); 

      IEnumerator<Dummy> ieNumerator = ieNumerable.GetEnumerator(); 
      long res = 0; 
      while (ieNumerator.MoveNext()) res += ieNumerator.Current.Val; 
      ieNumerator=ieNumerable.GetEnumerator(); 
      while (ieNumerator.MoveNext()) res -= (long)(ieNumerator.Current.Val * 0.3); 

      watch.Stop(); 
      Console.Write("\r\n\nCheck sum: " + res.ToString() + "\t iteration ticks :" + watch.ElapsedTicks); 
     } 
     static void Generate() 
     { 
      if (list.Count > 0) 
       return; 
      var rand = new Random(); 
      for (int i = 0; i < ElCount; i++) 
       list.Add(new Dummy(rand.Next(MaxVal))); 

     } 
     static void For() 
     { 
      List<Dummy> resList = new List<Dummy>(); 
      int count = list.Count; 
      for (int i = 0; i < count; i++) 
      { 
       if (list[i].Val < FilterVal) 
        resList.Add(list[i]); 
      }  
      Check(resList); 
     } 
     static void Foreach() 
     { 
      List<Dummy> resList = new List<Dummy>(); 
      int count = list.Count; 
      foreach (Dummy dummy in list) 
      { 
       if (dummy.Val < FilterVal) 
        resList.Add(dummy); 
      } 
      Check(resList); 
     } 
     static void WhereToList() 
     { 
      List<Dummy> resList = list.Where(x => x.Val < FilterVal).ToList<Dummy>(); 
      Check(resList); 
     } 
     static void WhereIEnumerable() 
     { 
      Stopwatch watch = new Stopwatch(); 
      IEnumerable<Dummy> iEnumerable = list.Where(x => x.Val < FilterVal); 
      Check(iEnumerable); 
     } 
     static void FindAll() 
     { 
      List<Dummy> resList = list.FindAll(x => x.Val < FilterVal); 
      Check(resList); 
     } 
     public static void Run() 
     { 
      Generate(); 
      long[] ticks = { 0, 0, 0, 0, 0 }; 
      for (int i = 0; i < 10; i++) 
      { 
       ticks[0] += TestTicks(For, "For \t\t"); 
       ticks[1] += TestTicks(Foreach, "Foreach \t"); 
       ticks[2] += TestTicks(WhereToList, "Where to list \t"); 
       ticks[3] += TestTicks(WhereIEnumerable, "Where Ienum \t"); 
       ticks[4] += TestTicks(FindAll, "FindAll \t"); 
       Console.Write("\r\n---------------"); 
      } 
      for (int i = 0; i < 5; i++) 
       Console.Write("\r\n"+ticks[i].ToString()); 
     } 

    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      WhereOrFindAll.Run(); 
      Console.Read(); 
     } 
    } 
} 

Risultati (zecche) - CheckSum abilitato (alcune operazioni sui risultati), la modalità: rilascio senza debug (CTRL + F5):

  • 16222276 (per -> Elenco)
  • 17151121 (foreach -> elenco)
  • 4741494 (dove -> elenco)
  • 27122285 (dove -> ienum)
  • 18 821.571 (findall -> Elenco)

CheckSum disabilitata (non si utilizza lista restituita a tutti):

  • 10885004 (per -> Elenco)
  • 11221888 (foreach -> Elenco)
  • 18688433 (dove -> elenco)
  • 1075 (dove -> ienum)
  • 13720243 (findall -> elenco)

I risultati possono essere leggermente diversi, per ottenere risultati reali sono necessarie più iterazioni.

+0

i tuoi test vanno bene. Mostrano che il meccanismo LINQ è più lento di operare direttamente sulla lista. Non una sorpresa. Il tuo "1075 (dove -> ienum)" è sbagliato in quello che usa un dove senza aver attraversato gli elementi risultanti non eseguirà mai realmente un dove! –

+1

Scusate Carlo, ma chiama il suo metodo "Check()" anche nel dove-> ienum implementatin. Check() itera tutte le raccolte, quindi i suoi risultati SONO interamente validi. Di conseguenza, anche la mia risposta è corretta ... la risposta che hai chiamato "torto". – jrista