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); }
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
.
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
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é)