2011-12-29 8 views
60

que estoy haciendo algunas pruebas de rendimiento y notó que una expresión LINQ como¿Por qué LINQ .Where (predicate) .First() es más rápido que .Primero (predicado)?

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

es más lento que

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

Esto parece contrario a la intuición. Hubiera pensado que la primera expresión sería más rápida porque puede detener la iteración sobre la lista tan pronto como se satisfaga el predicado, mientras que yo hubiera pensado que la expresión .Where() podría iterar en toda la lista antes de llamar al .First() en el subconjunto resultante. Incluso si este último hace un cortocircuito, no debería ser más rápido que usar First directamente, pero lo es.

A continuación se muestran dos pruebas de unidad realmente simples que ilustran esto. Cuando se compila con optimización en TestWhereAndFirst es aproximadamente 30% más rápido que TestFirstOnly en .Net y Silverlight 4. He intentado hacer que el predicado arroje más resultados, pero la diferencia de rendimiento es la misma.

¿Alguien puede explicar por qué .First(fn) es más lento que ? Veo un resultado contrario a la intuición similar con .Count(fn) en comparación con .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); 
} 
+4

¿Cómo lo calcula? –

+3

Sin embargo, su pensamiento inicial es incorrecto: LINQ hace cálculos perezosos, por lo que cuando se llama a 'First()' consultará (el valor de retorno) 'Where (...)' para una sola coincidencia y nunca solicitará otra. Por lo tanto, se examinará exactamente la misma cantidad de elementos que cuando se llama 'Primero (...)' (es decir, directamente con un predicado). – Jon

+1

Obtengo el mismo resultado, '.Where(). First()' es .021 segundos y '.First()' es .037 segundos. Esto es con una simple lista de 'int's. – Ryan

Respuesta

43

Tengo los mismos resultados: donde + primero fue más rápido que el primero.

Como notó Jon, Linq utiliza evaluación diferida por lo que el rendimiento debería ser (y es) muy similar para ambos métodos.

Al buscar en Reflector, First utiliza un bucle foreach para iterar a través de la colección, pero Where tiene una variedad de iteradores especializados para diferentes tipos de colecciones (matrices, listas, etc.). Presumiblemente esto es lo que le da a Donde la pequeña ventaja.

+0

Estoy al tanto de la evaluación perezosa. Hubiera pensado que con una evaluación perezosa perfecta, Where + First daría exactamente el mismo rendimiento que First. Su investigación con Reflector es muy útil, gracias. Esto ciertamente explica por qué First solo es más lento. Me pregunto por qué Microsoft decidió hacer solo dónde usar los iteradores especializados? – dazza

+0

@ user1120411: Sí, pero Primero (con un predicado) y Dónde elegir hacer básicamente lo mismo de diferentes maneras, por lo que el rendimiento es diferente. – arx

+7

Pero si fuera un desarrollador de frameworks y acabara de implementar First (fn) internamente como return Where (fn) .First() funcionará exactamente como la implementación actual de First, ¡excepto que más rápido! Parece una mala supervisión por parte de Microsoft. – dazza

Cuestiones relacionadas