FindAll vs Where extension-method

Je veux juste savoir si un “FindAll” sera plus rapide qu’un “Où” extentionMethod et pourquoi?

Exemple :

myList.FindAll(item=> item.category == 5); 

ou

 myList.Where(item=> item.category == 5); 

Ce qui est mieux ?

Eh bien, FindAll copie les éléments correspondants dans une nouvelle liste, alors que Where ne retourne qu’une séquence évaluée paresseusement – aucune copie n’est requirejse.

Je m’attendrais donc à ce que Where soit légèrement plus rapide que FindAll même lorsque la séquence résultante est pleinement évaluée – et bien sûr, la stratégie d’évaluation paresseuse de Where signifie que si vous ne regardez que (disons) le premier match, il n’aura pas besoin de vérifiez le rest de la liste. (Comme le souligne Matthew, la maintenance de la machine à états pour Where un travail Where . Toutefois, le coût de la mémoire sera fixe – alors que la construction d’une nouvelle liste peut nécessiter plusieurs affectations de tableaux, etc.)

À la base, FindAll(predicate) est plus proche de Where(predicate).ToList() que de Where(predicate) .

Juste pour réagir un peu plus à la réponse de Matthew, je ne pense pas qu’il l’ait suffisamment bien testée. Son prédicat arrive à choisir la moitié des objects. Voici un programme court mais complet qui teste la même liste, mais avec trois prédicats différents: on ne sélectionne aucun élément, on sélectionne tous les éléments et on en choisit la moitié. Dans chaque cas, je lance le test cinquante fois pour obtenir un temps plus long.

J’utilise Count() pour m’assurer que le résultat Where est complètement évalué. Les résultats montrent que, recueillant environ la moitié des résultats, ils sont au coude à coude. En ne recueillant aucun résultat, FindAll gagne. Rassembler tous les résultats, Where gagne. Je trouve cela insortingguant: toutes les solutions deviennent de plus en plus lentes à mesure que de plus en plus de correspondances sont trouvées: FindAll a davantage de copie à faire et Where doit renvoyer les valeurs correspondantes au lieu de simplement boucler dans l’ MoveNext() . Cependant, FindAll plus rapidement que Where , ce qui lui fait perdre son avance. Très intéressant.

Résultats:

 FindAll: All: 11994 Where: All: 8176 FindAll: Half: 6887 Where: Half: 6844 FindAll: None: 3253 Where: None: 4891 

(Compilé avec / o + / debug- et exécuté à partir de la ligne de commande, .NET 3.5.)

Code:

 using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; class Test { static List ints = Enumerable.Range(0, 10000000).ToList(); static void Main(ssortingng[] args) { Benchmark("All", i => i >= 0); // Match all Benchmark("Half", i => i % 2 == 0); // Match half Benchmark("None", i => i < 0); // Match none } static void Benchmark(string name, Predicate predicate) { // We could just use new Func(predicate) but that // would create one delegate wrapping another. Func func = (Func) Delegate.CreateDelegate(typeof(Func), predicate.Target, predicate.Method); Benchmark("FindAll: " + name, () => ints.FindAll(predicate)); Benchmark("Where: " + name, () => ints.Where(func).Count()); } static void Benchmark(ssortingng name, Action action) { GC.Collect(); Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < 50; i++) { action(); } sw.Stop(); Console.WriteLine("{0}: {1}", name, sw.ElapsedMilliseconds); } } 

Et si on testait au lieu de deviner? Dommage de voir la mauvaise réponse sortir.

 var ints = Enumerable.Range(0, 10000000).ToList(); var sw1 = Stopwatch.StartNew(); var findall = ints.FindAll(i => i % 2 == 0); sw1.Stop(); var sw2 = Stopwatch.StartNew(); var where = ints.Where(i => i % 2 == 0).ToList(); sw2.Stop(); Console.WriteLine("sw1: {0}", sw1.ElapsedTicks); Console.WriteLine("sw2: {0}", sw2.ElapsedTicks); /* Debug sw1: 1149856 sw2: 1652284 Release sw1: 532194 sw2: 1016524 */ 

Modifier:

Même si je tourne le code ci-dessus de

 var findall = ints.FindAll(i => i % 2 == 0); ... var where = ints.Where(i => i % 2 == 0).ToList(); 

… à …

 var findall = ints.FindAll(i => i % 2 == 0).Count; ... var where = ints.Where(i => i % 2 == 0).Count(); 

J’obtiens ces résultats

 /* Debug sw1: 1250409 sw2: 1267016 Release sw1: 539536 sw2: 600361 */ 

Edit 2.0 …

Si vous voulez une liste du sous-ensemble de la liste actuelle, la méthode la plus rapide est FindAll (). La raison en est simple. La méthode d’instance FindAll utilise l’indexeur de la liste actuelle au lieu de la machine d’état de l’énumérateur. La méthode d’extension Where () est un appel externe à une classe différente qui utilise l’énumérateur. Si vous passez de chaque nœud de la liste au nœud suivant, vous devrez appeler la méthode MoveNext () sous les couvertures. Comme vous pouvez le constater à partir des exemples ci-dessus, il est encore plus rapide d’utiliser les entrées d’index pour créer une nouvelle liste (qui pointe vers les éléments d’origine, de sorte que la mémoire mémoire sera minimale) pour obtenir un nombre d’éléments filtrés.

Maintenant, si vous allez abandonner tôt de l’énumérateur, la méthode Where () pourrait être plus rapide. Bien sûr, si vous déplacez la logique d’abandon précédent vers le prédicat de la méthode FindAll (), vous utiliserez à nouveau l’indexeur au lieu de l’énumérateur.

Il existe maintenant d’autres raisons d’utiliser l’instruction Where () (telles que les autres méthodes linq, les blocs foreach et bien d’autres), mais la question était de savoir que FindAll () était plus rapide que Where (). Et à moins que vous n’exécutiez pas Where (), la réponse semble être oui. (Quand on compare des pommes avec des pommes)

Je ne dis pas que n’utilisez pas LINQ ou la méthode .Where (). Ils font pour le code qui est beaucoup plus simple à lire. La question portait sur les performances et non sur la facilité avec laquelle vous pouvez lire et comprendre le code. De manière rapide, le moyen le plus rapide d’effectuer ce travail consiste à utiliser un bloc pour chaque index et la logique de votre choix (même les sorties anticipées). La raison pour laquelle LINQ est si formidable est due aux arbres d’expression complexes et à la transformation que vous pouvez obtenir avec eux. Mais utiliser l’iterator de la méthode .Where () doit passer par des tonnes de code pour trouver son chemin vers une machine mémoire en mémoire qui extrait juste le prochain index de la liste. Il convient également de noter que cette méthode .FindAll () n’est utile que sur les objects qui l’implémentent (tels que Array et List).

Encore plus …

 for (int x = 0; x < 20; x++) { var ints = Enumerable.Range(0, 10000000).ToList(); var sw1 = Stopwatch.StartNew(); var findall = ints.FindAll(i => i % 2 == 0).Count; sw1.Stop(); var sw2 = Stopwatch.StartNew(); var where = ints.AsEnumerable().Where(i => i % 2 == 0).Count(); sw2.Stop(); var sw4 = Stopwatch.StartNew(); var cntForeach = 0; foreach (var item in ints) if (item % 2 == 0) cntForeach++; sw4.Stop(); Console.WriteLine("sw1: {0}", sw1.ElapsedTicks); Console.WriteLine("sw2: {0}", sw2.ElapsedTicks); Console.WriteLine("sw4: {0}", sw4.ElapsedTicks); } /* averaged results sw1 575446.8 sw2 605954.05 sw3 394506.4 /* 

Au moins, vous pouvez essayer de le mesurer.

La méthode statique Where est mise en œuvre à l’aide d’un bloc d’iterator (mot-clé de yield ), ce qui signifie essentiellement que l’exécution sera différée. Si vous ne comparez que les appels à ces deux méthodes, la première sera plus lente, car elle implique immédiatement que toute la collection sera itérée.

Mais si vous incluez l’itération complète des résultats obtenus, les choses peuvent être un peu différentes. Je suis presque sûr que la solution de yield est plus lente, en raison du mécanisme de machine à états généré qu’elle implique. (voir @Matthew Anwser)

Je peux donner un indice, mais je ne sais pas lequel plus rapidement. FindAll () est exécuté immédiatement. Où () est différé, exécuté.

L’avantage de où est l’exécution différée. Voyez la différence si vous avez les fonctionnalités suivantes

 BigSequence.FindAll( x => DoIt(x) ).First(); BigSequence.Where( x => DoIt(x) ).First(); 

FindAll a couvert la séquence complète, tandis que Where, dans la plupart des séquences, cesse d’énumérer dès qu’un élément est trouvé.

Les mêmes effets seront ceux utilisant Any (), Take (), Skip (), etc. Je ne suis pas sûr, mais je suppose que vous aurez d’énormes avantages dans toutes les fonctions qui ont différé l’exécution