La méthode LINQ Count()
est-elle plus rapide ou plus lente que List<>.Count
ou Array.Length
?
En général plus lent. Le décompte général de LINQ est une opération O(N)
tandis que List.Count
et Array.Length
sont tous deux garantis d'être O(1)
.
Cependant, dans certains cas, LINQ accordera une attention particulière au paramètre IEnumerable<T>
en définissant certains types d’interface tels que IList<T>
ou ICollection<T>
. Il utilisera ensuite cette méthode Count pour effectuer une opération réelle Count()
. Donc, ça va redescendre à O(1)
. Mais vous payez toujours les frais généraux mineurs de la distribution et de l'appel d'interface.
La méthode Enumerable.Count()
recherche ICollection<T>
, à l'aide de .Count
. Ainsi, dans le cas des tableaux et des listes, ce n'est pas beaucoup plus inefficace (juste un niveau supplémentaire d'indirection).
Marc a la bonne réponse mais le diable est dans les détails.
Sur ma machine:
IList<T>
Les tableaux démarrent plus lentement car .Length n'implique qu'une seule opération. Le compte sur des tableaux implique une couche d'indirection. Ainsi, .Count sur les tableaux démarre 10 fois plus lentement (sur ma machine), ce qui pourrait être l'une des raisons pour lesquelles l'interface est implémentée explicitement. Imaginez si vous aviez un objet avec deux propriétés publiques, .Count et .Length. Les deux font exactement la même chose mais .Count est 10X plus lent.
Bien sûr, rien de tout cela fait vraiment une grande différence car il faudrait compter vos tableaux et listes des millions de fois par seconde pour ressentir un coup dur pour la performance.
Code:
static void TimeAction(string description, int times, Action func) {
var watch = new Stopwatch();
watch.Start();
for (int i = 0; i < times; i++) {
func();
}
watch.Stop();
Console.Write(description);
Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
}
static void Main(string[] args) {
var array = Enumerable.Range(0, 10000000).ToArray();
var list = Enumerable.Range(0, 10000000).ToArray().ToList();
// jit
TimeAction("Ignore and jit", 1 ,() =>
{
var junk = array.Length;
var junk2 = list.Count;
array.Count();
list.Count();
});
TimeAction("Array Length", 1000000, () => {
var tmp1 = array.Length;
});
TimeAction("Array Count()", 1000000, () =>
{
var tmp2 = array.Count();
});
TimeAction("Array Length through cast", 1000000, () =>
{
var tmp3 = (array as ICollection<int>).Count;
});
TimeAction("List Count", 1000000, () =>
{
var tmp1 = list.Count;
});
TimeAction("List Count()", 1000000, () =>
{
var tmp2 = list.Count();
});
Console.ReadKey();
}
Résultats:
Durée du tableau écoulée 3 ms Nombre de tableaux () Temps écoulé 264 ms Longueur du tableau après la conversion Temps écoulé 16 ms Lister le temps écoulé 3 ms Liste Compte () Temps écoulé 18 ms
Je crois que si vous appelez Linq.Count () sur un ICollection ou un IList (comme un ArrayList ou une List), il renverra simplement la valeur de la propriété Count. La performance sera donc à peu près la même sur les collections simples.
Je dirais que cela dépend de la liste. Si c'est un IQueryable qui est une table dans une base de données quelque part alors Count () sera beaucoup plus rapide car il n'a pas à charger tous les objets. Mais si la liste est en mémoire, j'imagine que la propriété Count serait plus rapide, sinon plus, identique.
Quelques informations supplémentaires - Compte LINQ - la différence entre l’utiliser et ne pas l’être peut être énorme - et cela ne doit pas nécessairement être non plus sur de grandes collections. J'ai une collection formée de linq à des objets avec environ 6500 articles (gros .. mais pas énorme par tout moyen). Count () dans mon cas prend plusieurs secondes. La conversion en liste (ou tableau, peu importe), le compte est alors quasi immédiate. Avoir ce nombre dans une boucle interne signifie que l'impact pourrait être énorme. Le comte énumère à travers tout. Un tableau et une liste sont tous deux "conscients" de leur longueur et n'ont pas besoin de les énumérer. Toute déclaration de débogage (log4net par exemple) faisant référence à ce compte () ralentira également considérablement le tout. Faites-vous une faveur et si vous avez besoin de référencer, enregistrez souvent la taille du nombre et appelez-la une seule fois sur une collection LINQ, à moins que vous ne la convertissiez en liste et que vous puissiez ensuite faire référence à distance sans impact négatif sur les performances.
Voici un rapide test de ce dont je parlais ci-dessus. Notez que chaque fois que nous appelons Count (), la taille de notre collection change. L’évaluation a donc lieu, ce qui est plus que l’opération «count» attendue. Juste quelque chose à être au courant de:)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace LinqTest
{
class TestClass
{
public TestClass()
{
CreateDate = DateTime.Now;
}
public DateTime CreateDate;
}
class Program
{
static void Main(string[] args)
{
//Populate the test class
List list = new List(1000);
for (int i=0; i<1000; i++)
{
System.Threading.Thread.Sleep(20);
list.Add(new TestClass());
if(i%100==0)
{
Console.WriteLine(i.ToString() + " items added");
}
}
//now query for items
var newList = list.Where(o=> o.CreateDate.AddSeconds(5)> DateTime.Now);
while (newList.Count() > 0)
{
//Note - are actual count keeps decreasing.. showing our 'execute' is running every time we call count.
Console.WriteLine(newList.Count());
System.Threading.Thread.Sleep(500);
}
}
}
}
List.Count
ou Array.Length
est en effet plus rapide que Linq Count()
. Parce que Linq Count()
parcourra toute la liste des éléments à compter. List.Count
ou Array.Length
utilisent leur propriété.