Te has preguntado alguna vez la diferencia de rendimiento que pueda haber entre el método extensor Count() proporcionado por LINQ y la propiedad Count de la interfaz IList<T>.
Es decir dado el siguiente código:
List<int> lst = new List<int>();
// Añadimos ints a la lista...
// Qué es más rápido?
var count = lst.Count;
var count2 = ((IEnumerable<int>)lst).Count();
A veces hacemos suposiciones sobre como funciona LINQ to objects. Uno puede pensar que el método Count() de LINQ está definido como:
public static int Count<T>(this IEnumerable<T> @this)
{
int count = 0;
foreach (var x in @this) count++;
return count;
}
Hay gente que basándose en estas suposiciornes intenta evitar el uso de Count() cuando sabe que la colección real es una List<T> p.ej. Desgraciadamente esto les lleva a no poder hacer métodos genéricos con IEnumerable<T> (empiezan a trabajar con IList<T>). A veces comentan que usarían mucho más LINQ to Objects, pero que trabajan habitualmente con listas, y que no pueden permitirse el sobrecoste de recorrer toda la lista simplemente para contar los elementos, cuando la clase List<T> ya tiene una propiedad para ello…
… están totalmente equivocados.
LINQ to Objects está optimizado, no es un proveedor tan tonto como algunos piensan… Así realmente si el objeto sobre el que usamos Count() implementa ICollection o ICollection<T>, LINQ usará la propiedad Count directamente, sin recorrer los elementos.
Para que veais que es cierto he realizado un pequeño test:
class Program
{
static void Main(string[] args)
{
List<int> list = new List<int>();
for (int i = 0; i < 10000000; i++)
{
list.Add(i);
}
Stopwatch sw = new Stopwatch();
sw.Start();
CountList(list);
sw.Stop();
Console.WriteLine("List.Count:" + sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
CountLinq(list);
sw.Stop();
Console.WriteLine("LINQ.Count():" + sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
CountLoop(list);
sw.Stop();
Console.WriteLine("foreach count" + sw.ElapsedMilliseconds);
sw.Reset();
Console.ReadLine();
}
static void CountList (IList<int> list)
{
for (int i=0; i< 100; i++)
{
var a = list.Count;
}
}
static void CountLinq(IEnumerable<int> list)
{
for (int i = 0; i < 100; i++)
{
var a = list.Count();
}
}
static void CountLoop(IEnumerable<int> list)
{
for (int i = 0; i < 100; i++)
{
var a = list.Count2();
}
}
}
El test cuenta 100 veces una lista con 10 millones de elementos, y cuenta lo que se tarda usando la propiedad Count de la lista, el método Count() de LINQ y el método Count2, que es un método extensor que recorre la lista (es exactamente el mismo método que he puesto antes).
Los resultados no dejan lugar a dudas:
- Usando la propiedad Count, se tarda menos de un ms en contar 100 veces la lista.
- Usando el método Count() de LINQ se tarda igualmente menos de un ms en contar la lista 100 veces.
- Usando el método extensor Count2 se tarda más de 9 segundos en contar la lista 100 veces…
Si en lugar de 100 veces la contamos diez millones de veces, los resultados son:
- 30 ms usando la propiedad Count
- 247 ms usando el método Count() de LINQ
- Ni idea usando el método extensor Count2… pero vamos si para 100 veces ha tardado 9 segundos… para diez millones… no quiero ni pensarlo!
Los tiempos han sido medidos con la aplicación en Release.
La conclusión es clara: no tengáis miedo a LINQ, que MS no ha hecho algo tan cutre como un triste foreach!! ;-)
Saludos!
PD: En este post del blog del equipo de C# cuentan esta y otras optimizaciones más de LINQ to Objects… lectura imprescindible! :)
PD2: Esto es un crossposting de mi blog geeks.ms!
No hay comentarios:
Publicar un comentario