156 votes

Performances de Find () par rapport à FirstOrDefault ()

Double Possible:
Find() vs Où().FirstorDefault()

A obtenu un intéressant résultat de la recherche pour Diana, au sein d'une grande séquence d'un simple type référence ayant une chaîne unique de la propriété.

Stopwatch watch = new Stopwatch();        
string 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().ToString()
                     }).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<T>.Find()
    watch.Restart();
    customers.Find(c => c.Name == diana);
    watch.Stop();
    Console.WriteLine("Diana was found in {0} ms with System.Collections.Generic.List<T>.Find().", watch.ElapsedMilliseconds);
}

enter image description here

Est-ce à cause de pas d'Énumérateur de frais généraux dans la Liste.Find() ou ce plus peut-être quelque chose d'autre?

Find() fonctionne à peu près deux fois plus vite, en espérant .Net de l'équipe ne marque pas Obsolète dans l'avenir.

154voto

devshorts Points 2741

J'ai été en mesure d'imiter vos résultats donc j'ai décompilé votre programme et il y a une différence entre Find et FirstOrDefault.

Tout d'abord voici la décompilation du programme. J'ai fait votre objet de données d'une anonmyous élément de données juste pour la compilation

    List<\u003C\u003Ef__AnonymousType0<string>> source = Enumerable.ToList(Enumerable.Select(Enumerable.Range(0, 1000000), i =>
    {
      var local_0 = new
      {
        Name = Guid.NewGuid().ToString()
      };
      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<T>.Find().", (object) stopwatch.ElapsedMilliseconds);

La clé de la chose à noter ici est que l' FirstOrDefault est appelée sur Enumerable alors qu' Find s'appelle une méthode sur la liste source.

Alors, qu'est-ce que trouver en train de faire? C'est le décompilé Find méthode

private T[] _items;

[__DynamicallyInvokable]
public T Find(Predicate<T> 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);
}

C'est donc une itération sur un tableau d'éléments qui fait sens, comme une liste est un wrapper sur un tableau.

Toutefois, FirstOrDefault, sur l' Enumerable classe, utilise foreach pour itérer les éléments. Il utilise un itérateur à la liste et passer à côté. Je pense que ce que vous voyez est la surcharge de l'itérateur

[__DynamicallyInvokable]
public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> 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 syntatic sucre sur l'utilisation de l'énumérable modèle. Regardez cette image

enter image description here.

J'ai cliqué sur une boucle foreach pour voir ce qu'il fait et vous pouvez le voir dotpeek veut m'emmener à l'agent recenseur/en cours/suivant les implémentations qui a du sens.

Sauf qu'ils sont fondamentalement les mêmes (les tests passés dans prédicat pour voir si un élément est ce que vous voulez)

34voto

Chris Sinclair Points 14829

Je suis le pari que FirstOrDefault est en cours d'exécution via l' IEnumerable mise en œuvre, qui est, il utilisera la norme foreach boucle pour effectuer la vérification. List<T>.Find() n'est pas une partie de Linq (http://msdn.microsoft.com/en-us/library/x0b5b5bc.aspx), et il est probable qu'à l'aide d'une norme for boucle d' 0 de Count (ou un autre rapide mécanisme interne probablement d'exploitation directement sur son interne/tableau renvoyé à la ligne). En se débarrassant de la surcharge de l'énumération d' (et de faire la version des vérifications pour s'assurer que la liste n'a pas été modifié) l' Find méthode est plus rapide.

Si vous ajoutez un troisième test:

//3. System.Collections.Generic.List<T> foreach
Func<Customer, bool> 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<T> foreach.", watch.ElapsedMilliseconds);

Qui s'exécute sur la même vitesse que le premier (25ms vs 27 ms est observée pour FirstOrDefault)

EDIT: Si j'ajoute une boucle de tableau, c'est assez proche de l' Find() de la vitesse, et compte tenu de @devshorts coup d'oeil au code source, je pense que c'est ça:

//4. System.Collections.Generic.List<T> 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 va seulement 5,5% plus lent que l' Find() méthode.

Donc la ligne en bas: une boucle sur les éléments du tableau est plus rapide que de traiter avec des foreach itération de frais généraux. (mais les deux ont leurs avantages/inconvénients, il suffit donc de choisir ce qui fait sens pour votre code logiquement. En outre, ne sont que rarement le peu de différence de vitesse jamais la cause d'un problème, utilisez donc ce qui fait sens pour la maintenabilité et la lisibilité)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X