Performance de Find () vs FirstOrDefault ()

Question similaire:
Find () vs. Where (). FirstOrDefault ()

Vous avez trouvé un résultat intéressant à la recherche de Diana dans une grande séquence d’un type de référence simple ayant une propriété de chaîne unique.

using System; using System.Collections.Generic; using System.Linq; public class Customer{ public ssortingng Name {get;set;} } Stopwatch watch = new Stopwatch(); const ssortingng diana = "Diana"; while (Console.ReadKey().Key != ConsoleKey.Escape) { //Armour with 1000k++ customers. Wow, should be a product with a great success! :) var customers = (from i in Enumerable.Range(0, 1000000) select new Customer { Name = Guid.NewGuid().ToSsortingng() }).ToList(); customers.Insert(999000, new Customer { Name = diana }); // Putting Diana at the end :) //1. System.Linq.Enumerable.DefaultOrFirst() watch.Restart(); customers.FirstOrDefault(c => c.Name == diana); watch.Stop(); Console.WriteLine("Diana was found in {0} ms with System.Linq.Enumerable.FirstOrDefault().", watch.ElapsedMilliseconds); //2. System.Collections.Generic.List.Find() watch.Restart(); customers.Find(c => c.Name == diana); watch.Stop(); Console.WriteLine("Diana was found in {0} ms with System.Collections.Generic.List.Find().", watch.ElapsedMilliseconds); } 

entrer la description de l'image ici

Est-ce à cause de la surcharge Enumerator dans List.Find () ou ce plus peut-être quelque chose d’autre?

Find() fonctionne presque deux fois plus vite, en espérant que l’équipe .Net ne l’annoncera pas à l’avenir.

J’ai été capable d’imiter vos résultats, alors j’ai décompilé votre programme et il y a une différence entre Find et FirstOrDefault .

Tout d’abord, voici le programme décompilé. J’ai fait de votre object de données un élément de données anonyme pour la compilation

  List<\u003C\u003Ef__AnonymousType0> source = Enumerable.ToList(Enumerable.Select(Enumerable.Range(0, 1000000), i => { var local_0 = new { Name = Guid.NewGuid().ToSsortingng() }; return local_0; })); source.Insert(999000, new { Name = diana }); stopwatch.Restart(); Enumerable.FirstOrDefault(source, c => c.Name == diana); stopwatch.Stop(); Console.WriteLine("Diana was found in {0} ms with System.Linq.Enumerable.FirstOrDefault().", (object) stopwatch.ElapsedMilliseconds); stopwatch.Restart(); source.Find(c => c.Name == diana); stopwatch.Stop(); Console.WriteLine("Diana was found in {0} ms with System.Collections.Generic.List.Find().", (object) stopwatch.ElapsedMilliseconds); 

L’essentiel à noter ici est que FirstOrDefault est appelé sur Enumerable alors que Find est appelé comme méthode sur la liste source.

Alors, que trouve-t-on? C’est la méthode Find décompilée

 private T[] _items; [__DynamicallyInvokable] public T Find(Predicate match) { if (match == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match); for (int index = 0; index < this._size; ++index) { if (match(this._items[index])) return this._items[index]; } return default (T); } 

Il est donc itératif sur un tableau d'éléments, car une liste est un wrapper sur un tableau.

Cependant, FirstOrDefault , sur la classe Enumerable , utilise foreach pour itérer les éléments. Cela utilise un iterator dans la liste et se déplace ensuite. Je pense que ce que vous voyez est la surcharge de l'iterator

 [__DynamicallyInvokable] public static TSource FirstOrDefault(this IEnumerable source, Func predicate) { if (source == null) throw Error.ArgumentNull("source"); if (predicate == null) throw Error.ArgumentNull("predicate"); foreach (TSource source1 in source) { if (predicate(source1)) return source1; } return default (TSource); } 

Foreach est juste du sucre syntatique en utilisant le modèle énumérable. Regardez cette image

entrer la description de l'image ici .

J'ai cliqué sur foreach pour voir ce qu'il faisait et vous pouvez voir que dotpeek veut m'amener à l'énumérateur / aux implémentations actuelles / suivantes, ce qui est logique.

En dehors de cela, ils sont fondamentalement les mêmes (tester le prédicat passé pour voir si un élément est ce que vous voulez)

Je FirstOrDefault que FirstOrDefault s’exécute via l’implémentation IEnumerable , c’est-à-dire qu’il utilisera une boucle foreach standard pour effectuer la vérification. List.Find() ne fait pas partie de Linq ( http://msdn.microsoft.com/en-us/library/x0b5b5bc.aspx ) et utilise probablement une norme for boucle de 0 à Count (ou une autre mécanisme interne rapide fonctionnant probablement directement sur son tableau interne / enveloppé). En éliminant la surcharge liée à l’énumération (et en effectuant les vérifications de version pour s’assurer que la liste n’a pas été modifiée), la méthode Find est plus rapide.

Si vous ajoutez un troisième test:

 //3. System.Collections.Generic.List foreach Func dianaCheck = c => c.Name == diana; watch.Restart(); foreach(var c in customers) { if (dianaCheck(c)) break; } watch.Stop(); Console.WriteLine("Diana was found in {0} ms with System.Collections.Generic.List foreach.", watch.ElapsedMilliseconds); 

Cela tourne à peu près à la même vitesse que le premier (25ms vs 27ms pour FirstOrDefault )

EDIT: Si j’ajoute une boucle de tableau, elle devient assez proche de la vitesse de Find() , et vu que @devshorts jette un coup d’oeil sur le code source, je pense que c’est ça:

 //4. System.Collections.Generic.List for loop var customersArray = customers.ToArray(); watch.Restart(); int customersCount = customersArray.Length; for (int i = 0; i < customersCount; i++) { if (dianaCheck(customers[i])) break; } watch.Stop(); Console.WriteLine("Diana was found in {0} ms with an array for loop.", watch.ElapsedMilliseconds); 

Cela ne fonctionne que 5,5% plus lentement que la méthode Find() .

Donc, la ligne de bas de page à travers des éléments de tableau est plus rapide que de traiter avec une surcharge d’itération. (mais les deux ont leurs avantages / inconvénients, choisissez simplement ce qui est logique pour votre code en toute logique. De plus, la petite différence de vitesse ne causera que rarement un problème, alors utilisez simplement ce qui est logique pour la maintenabilité / lisibilité)