web-dev-qa-db-fra.com

Pourquoi LINQ .Where (predicate) .First () est plus rapide que .First (predicate)?

Je fais des tests de performances et j'ai remarqué qu'une expression LINQ comme

result = list.First(f => f.Id == i).Property

est plus lent que

result = list.Where(f => f.Id == i).First().Property

Cela semble contre-intuitif. J'aurais pensé que la première expression serait plus rapide car elle peut arrêter d'itérer sur la liste dès que le prédicat est satisfait, alors que j'aurais pensé que l'expression .Where() pourrait parcourir toute la liste avant d'appeler .First() sur le sous-ensemble résultant. Même si ce dernier fait un court-circuit, il ne devrait pas être plus rapide que d'utiliser First directement, mais il l'est.

Voici deux tests unitaires vraiment simples qui illustrent cela. Lorsqu'il est compilé avec optimisation sur TestWhereAndFirst, il est environ 30% plus rapide que TestFirstOnly sur .Net et Silverlight 4. J'ai essayé de faire en sorte que le prédicat renvoie plus de résultats, mais la différence de performances est la même.

Quelqu'un peut-il expliquer pourquoi .First(fn) est plus lent que .Where(fn).First()? Je vois un résultat contre-intuitif similaire avec .Count(fn) par rapport à .Where(fn).Count().

private const int Range = 50000;

private class Simple
{
   public int Id { get; set; }
   public int Value { get; set; }
}

[TestMethod()]
public void TestFirstOnly()
{
   List<Simple> list = new List<Simple>(Range);
   for (int i = Range - 1; i >= 0; --i)
   {
      list.Add(new Simple { Id = i, Value = 10 });
   }

   int result = 0;
   for (int i = 0; i < Range; ++i)
   {
      result += list.First(f => f.Id == i).Value;
   }

   Assert.IsTrue(result > 0);
}

[TestMethod()]
public void TestWhereAndFirst()
{
   List<Simple> list = new List<Simple>(Range);
   for (int i = Range - 1; i >= 0; --i)
   {
      list.Add(new Simple { Id = i, Value = 10 });
   }

   int result = 0;
   for (int i = 0; i < Range; ++i)
   {
      result += list.Where(f => f.Id == i).First().Value;
   }

   Assert.IsTrue(result > 0);
}
69
dazza

J'ai obtenu les mêmes résultats: où + premier était plus rapide que premier.

Comme Jon l'a noté, Linq utilise une évaluation paresseuse, donc les performances devraient être (et sont) largement similaires pour les deux méthodes.

En regardant dans Reflector, First utilise une simple boucle foreach pour parcourir la collection, mais Where a une variété d'itérateurs spécialisés pour différents types de collections (tableaux, listes, etc.). C'est probablement ce qui donne à Where le petit avantage.

50
arx