2009-06-11 15 views
44

¿Es el método Linq Count() más rápido o más lento que List<>.Count o Array.Length?¿El conteo de Linq() es más rápido o más lento que List.Count o Array.Length?

+9

La manera más fácil de saber es probarlo. Envuelva ambos en llamadas a los métodos apropiados en Cronómetro, hágalo algunos millones de veces, y lo sabrá. –

+1

Probablemente no valga la pena que no haya una diferencia notable en la velocidad, a menos que hablemos de colecciones seriamente grandes. Simplemente use cualquiera que sea más fácil de leer/mantener. – Hardwareguy

Respuesta

58

En general Más lento. El recuento de LINQ en general es una operación O(N), mientras que List.Count y Array.Length tienen la garantía de ser O(1).

Sin embargo, en algunos casos LINQ será el caso especial del parámetro IEnumerable<T> mediante conversión a ciertos tipos de interfaz como IList<T> o ICollection<T>. A continuación, utilizará ese método Count para realizar una operación real Count(). Por lo tanto, volverá a bajar al O(1). Pero aún pagas la sobrecarga menor del reparto y la llamada a la interfaz.

+0

@Marc, agregué ese cavaet. – JaredPar

+0

No estoy seguro, pero creo que si List.Count() se ejecuta en un IQueryable ejecutará el comando select count (*) sql. pero si se ejecuta List.Count enumerará a través de todos los elementos y luego devolverá el conteo. Si este último, List.Count() sería más rápido la mayor parte del tiempo. – Jose

+0

@Jared, la respuesta de Marcs es más precisa, la comprobación solo se realiza para ICollection , arrays, HashSet, Dictionary, List, LinkedList y Queue todos implementan ICollection . Las antiguas clases System.Collection no lo hacen, pero de nuevo no implementa IEnumerable de todos modos, por lo que no se pueden usar con LINQ. –

2

Creo que si llama a Linq.Count() en una ICollection o IList (como una lista de arreglos o una lista), simplemente devolverá el valor de la propiedad Count. Por lo tanto, el rendimiento será aproximadamente el mismo en las colecciones simples.

+0

ArrayList no es IEnumerable por lo que no puede ejecutar métodos de extensión LINQ en él de todos modos. la comprobación solo se realiza para ICollection

25

El método Enumerable.Count() comprueba ICollection<T>, usando .Count - por lo que en el caso de matrices y listas, no es mucho más ineficiente (solo un nivel extra de indirección).

+0

En realidad con las matrices se obtienen 2 capas de direccionamiento indirecto, ver mi respuesta: p –

2

Yo diría que depende de la lista. Si es un IQueryable que es una tabla en un db en algún lugar, entonces Count() será mucho más rápido porque no tiene que cargar todos los objetos. Pero si la lista está en memoria, supongo que la propiedad Count sería más rápida si no es igual.

22

Marc tiene la respuesta correcta, pero el diablo está en los detalles.

En mi máquina:

  • Para las matrices .length es aproximadamente 100 veces más rápido que .Count()
  • para las listas .Count es aproximadamente 10 veces más rápido que .Count() - Nota: lo haría esperar un rendimiento similar de todas las colecciones que implementan IList<T>

matrices comienzan más lento desde .length implica únicamente una sola operación, .Count en arrays consiste en una capa de direccionamiento indirecto. Entonces, .Count en matrices comienza 10 veces más lento (en mi máquina), lo que podría ser una de esas razones por las que la interfaz se implementa explícitamente. Imagine si tuviera un objeto con dos propiedades públicas, .Count y .Length. Ambos hacen exactamente lo mismo pero .Count es 10 veces más lento.

Por supuesto, esto no hace mucha diferencia, ya que tendría que estar contando sus matrices y listas millones de veces por segundo para sentir un golpe de rendimiento.

Código:

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(); 
    } 

Resultados:

 
Array Length Time Elapsed 3 ms 
Array Count() Time Elapsed 264 ms 
Array Length through cast Time Elapsed 16 ms 
List Count Time Elapsed 3 ms 
List Count() Time Elapsed 18 ms 
+0

Afortunadamente 'collection.Count/Length' es más legible que' collection.Count() '. Casos raros donde el código más bonito es más eficiente: P – nawfal

+0

Solo FYI, veo una pequeña diferencia entre '(array como ICollection ) .Count;' y '(array como ICollection) .Count;' (a favor del primero). – jmoreno

0

algo de información adicional - LINQ contar - la diferencia entre usar y no puede ser enorme - y esto no tiene que ser más ' grandes colecciones tampoco. Tengo una colección formada por linq para objetos con aproximadamente 6500 elementos (grande ... pero no enorme de ninguna manera). Count() en mi caso toma varios segundos. Convirtiendo en una lista (o matriz, qué), el conteo es virtualmente inmediato. Tener esto en cuenta en un bucle interno significa que el impacto podría ser enorme. Count enumera todo.Una matriz y una lista son "autoconscientes" de sus longitudes y no es necesario enumerarlas. Cualquier declaración de depuración (log4net por ejemplo) que haga referencia a este conteo() también ralentizará considerablemente todo. Hágase un favor y si necesita referenciar esto a menudo, guarde el tamaño de conteo y solo llámelo una vez en una colección LINQ a menos que lo convierta en una lista y luego pueda hacer referencia sin un golpe de rendimiento.

Aquí hay una prueba rápida de lo que estaba hablando anteriormente. Nótese que cada vez que llamamos a Count() nuestro tamaño de colección cambia ... por lo tanto, la evaluación ocurre, que es más que una operación de 'recuento' esperada. Algo a tener en cuenta:)

 
    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); 
       } 
      } 
     } 
    } 
 
+2

list.Where devuelve un IEnumerable por lo que no obtienes accesos directos con Count() ... si lo materializaste verías muy bien perf (por ejemplo: 'list.Where (o => o.CreateDate.AddSeconds (5)> DateTime .Now) .ToList() ') –

Cuestiones relacionadas