2010-02-14 5 views
30

Tout le monde connaît les différences de vitesse entre Where et FindAll on List. Je sais où fait partie de IEnumerable et FindAll fait partie de List, je suis juste curieux de savoir ce qui est le plus rapide.C# FindAll VS Where Speed ​​

+0

duplication possible de [FindAll vs Où extension-méthode] (http://stackoverflow.com/questions/1531702/findall-vs-where-extension-method) –

Répondre

45

La méthode FindAll de la classe List <T> classe réellement un nouvel objet de liste et lui ajoute des résultats. La Où méthode d'extension pour IEnumerable <T> simplement itérer sur une liste existante et donne une énumération des résultats correspondants sans créer ou d'ajouter quoi que ce soit (autre que le recenseur lui-même.)

Étant donné un petit ensemble, les deux seraient probablement effectuer de manière comparable. Toutefois, étant donné un ensemble plus important, Where devrait surperformer FindAll, car la nouvelle liste créée pour contenir les résultats devra dynamiquement croître pour contenir des résultats supplémentaires. L'utilisation de la mémoire de FindAll commencera également à croître exponentiellement à mesure qu'augmentera le nombre de résultats correspondants, où l'utilisation de la mémoire devrait être minimale (en excluant tout ce que vous ferez avec les résultats.)

+19

L'exception est l'endroit où vous voulez réellement avoir une liste ensuite (peut-être vous avez besoin d'appeler "Count" ou de changer de membres, ou de parcourir it plus d'une fois). Alors que 'Where()' bat 'FindAll()', 'FindAll()' bat 'Where(). ToList()'. –

+5

@JonHanna: Alors que je pensais d'abord d'accord, je ne suis pas sûr. Avez-vous des références qui indiquent qu'un .ToList() est plus lent qu'un .FindAll()? Appeler .ToList() sur une requête serait ** l'itération de l'énumérable, et devrait donc conserver son efficacité mémoire. Non seulement cela, certaines implémentations internes de l'itérateur où même pourrait créer une liste de la bonne taille (allocation de mémoire) à l'avance, surpassant le FindAll dans de tels cas. Je ne suis pas particulièrement en désaccord, mais il serait bon d'avoir une référence solide qui clarifie FindAlls avantage. – jrista

+1

Cette réponse est complètement fausse. Voir @Wiory qui a pris la peine de mesurer réellement. –

3

être plus rapide, il profite de déjà connaître la taille de la liste et de boucler à travers le tableau interne avec une simple boucle for. .Where() doit déclencher un énumérateur (une classe de structure scellée appelée WhereIterator dans ce cas) et faire le même travail d'une manière moins spécifique. Cependant, gardez à l'esprit que .Where() est énumérable, ne crée pas activement une liste en mémoire et ne la remplit pas. C'est plus comme un flux, donc l'utilisation de la mémoire sur quelque chose de très grand peut avoir une différence significative. En outre, vous pouvez commencer à utiliser les résultats de manière parallèle beaucoup plus rapidement en utilisant l'approche .Where() en 4.0.

+1

Le WhereEnumerableIterator, plutôt que WhereIterator, est réellement utilisé sauf si vous impliquez index dans la clause where. WhereEnumerableIterator est considérablement plus efficace que WhereIterator. Dans le cas de la liste , elle entraîne le coût d'un appel de méthode supplémentaire (qui doit être incorporé dans le code de version), mais n'a pas besoin de redimensionner dynamiquement une liste interne dans le cadre de son traitement. L'efficacité de Where doit surpasser FindAll dans toutes les listes, sauf les plus petites (tout résultat supérieur à 4 résultats entraînera une ou plusieurs réimplantations.) – jrista

+0

Dans le cas de l'appel Où sur un tableau ou une liste , il existe deux classes d'itérateurs internes supplémentaires, WhereArrayIterator et WhereListIterator, qui sont optimisés pour ces deux cas. D'une manière générale, appeler Where devrait être plus efficace que d'appeler FindAll. – jrista

+2

@jrista - Je ** complètement ** raté la pile de cas dans le '.Où() 'surcharge retournant ceux, merci! En examinant ce code, je suis d'accord. Il devrait y avoir, au pire, une performance égale mais presque toujours meilleure. En outre, SO serait inutile si ce n'est pas pour les personnes qui prennent le temps supplémentaire d'éduquer les autres, par ex. toi et ces commentaires, +1 pour m'apprendre quelque chose. –

4

Where est beaucoup, beaucoup plus rapide que FindAll. Peu importe la taille de la liste, Where prend exactement le même temps.

Bien sûr, Where crée simplement une requête. Il ne fait rien, contrairement à FindAll qui crée une liste.

+5

Cela peut être techniquement vrai, mais je pense qu'il est assez clair que le PO pose des questions sur les performances dans le contexte de l'énumération du résultat, et non sur l'appel de la méthode nue elle-même. –

-3

La réponse de jrista fait sens. Cependant, la nouvelle liste ajoute les mêmes objets, augmentant ainsi simplement en référence aux objets existants, ce qui ne devrait pas être si lent. Tant que l'extension 3.5/Linq est possible, Où reste mieux quand même. FindAll a beaucoup plus de sens lorsqu'il est limité à 2.0

6

FindAll est évidemment plus lent que Where, car il doit créer une nouvelle liste.

Quoi qu'il en soit, je pense que vous devriez vraiment considérer Jon Hanna commentaire - vous aurez probablement besoin de faire quelques opérations sur vos résultats et la liste serait plus utile que IEnumerable dans de nombreux cas.

J'ai écrit un petit test, il suffit de le coller dans le projet Console App. Il mesure le temps/ticks de: l'exécution de la fonction, les opérations sur la collecte des résultats (pour obtenir la représentation de l'utilisation réelle, et pour s'assurer que le compilateur n'optimisera pas les données inutilisées, etc. sachez comment cela fonctionne encore, désolé).

Remarque: chaque fonction mesurée sauf WhereIENumerable() crée une nouvelle liste d'éléments. Je pourrais faire quelque chose de mal, mais clairement IERumerable iterating prend beaucoup plus de temps que la liste iterating.

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(); 
     } 
    } 
} 

Résultats (tiques) - CheckSum activée (certaines opérations sur les résultats), le mode: libération sans débogage (CTRL + F5):

  • 16222276 (pour -> Liste)
  • 17151121 (foreach -> liste)
  • 4741494 (où -> liste)
  • 27122285 (où -> ienum)
  • 18 821571 (findall -> Liste)

CheckSum désactivé (ne pas utiliser la liste de retour du tout):

  • 10885004 (pour -> Liste)
  • 11221888 (foreach -> Liste)
  • 18688433 (où -> liste)
  • 1075 (où -> ienum)
  • 13720243 (findall -> liste)

Vos résultats peuvent être légèrement différents, pour obtenir des résultats réels, vous avez besoin de plus d'itérations.

+0

vos tests sont bien. Ils montrent que le mécanisme LINQ est plus lent que d'opérer directement sur la liste. Pas une surprise. Votre "1075 (où -> ienum)" est faux en ce que l'utilisation d'un où sans traverser les éléments résultants ne sera jamais réellement effectuer un où! –

+1

Désolé Carlo, mais il appelle sa méthode "Check()" même dans l'implémentation where-> ienum. Check() itère toutes les collections, donc ses résultats sont entièrement valides. En conséquence, cela rend également ma réponse correcte ... la réponse que vous avez appelée "tort mort". – jrista