¿Linq es más eficiente de lo que parece en la superficie?

13

Si escribo algo como esto:

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue)

Es lo mismo que:

var results1 = new List<Thing>();
foreach(var t in mythings)
    if(t.IsSomeValue)
        results1.Add(t);

var results2 = new List<Thing>();
foreach(var t in results1)
    if(t.IsSomeOtherValue)
        results2.Add(t);

O hay algo de magia debajo de las cubiertas que funciona más así:

var results = new List<Thing>();
foreach(var t in mythings)
    if(t.IsSomeValue && t.IsSomeOtherValue)
        results.Add(t);

¿O es algo completamente diferente?

    
pregunta ConditionRacer 29.10.2013 - 16:13

4 respuestas

27
Las

consultas LINQ son flojas . Eso significa el código:

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue);

hace muy poco. El enumerable original ( mythings ) solo se enumera cuando se consume el enumerable resultante ( things ), por ejemplo. por un bucle foreach , .ToList() o .ToArray() .

Si llama a things.ToList() , es más o menos equivalente a su último código, con quizás una sobrecarga (generalmente insignificante) de los enumeradores.

Del mismo modo, si utiliza un bucle foreach:

foreach (var t in things)
    DoSomething(t);

Es similar en rendimiento a:

foreach (var t in mythings)
    if (t.IsSomeValue && t.IsSomeOtherValue)
        DoSomething(t);

Algunas de las ventajas de rendimiento del enfoque de holgazanería para los enumerables (en lugar de calcular todos los resultados y almacenarlos en una lista) son que utiliza muy poca memoria (ya que solo se almacena un resultado a la vez) y que hay no hay costos iniciales significativos.

Si el enumerable solo se enumera parcialmente, esto es especialmente importante. Considere este código:

things.First();

La forma en que se implementa LINQ, mythings solo se enumerará hasta el primer elemento que coincida con las condiciones de su ubicación. Si ese elemento se encuentra al principio de la lista, este puede ser un gran aumento del rendimiento (por ejemplo, O (1) en lugar de O (n)).

    
respondido por el Cyanfish 29.10.2013 - 16:35
7

El siguiente código:

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue);

No es equivalente a nada, debido a la perezosa evaluación, no pasará nada.

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue)
    .ToList();

Es diferente, porque la evaluación se iniciará.

Cada elemento de mythings se asignará al primer Where . Si pasa, se le dará al segundo Where . Si pasa, será parte de la salida.

Así que esto se parece más a esto:

var results = new List<Thing>();
foreach(var t in mythings)
{
    if(t.IsSomeValue)
    {
        if(t.IsSomeOtherValue)
        {
            results.Add(t);
        }
    }
}
    
respondido por el Cyril Gandon 29.10.2013 - 16:35
7

Dejando la ejecución diferida a un lado (como explican las otras respuestas, solo señalaré otro detalle), es más como en tu segundo ejemplo.

Imaginemos que llamas ToList en things .

La implementación de Enumerable.Where devuelve un Enumerable.WhereListIterator . Cuando llama a Where a ese WhereListIterator (a.k.a. encadenamiento Where -calls), ya no llama a Enumerable.Where , sino a Enumerable.WhereListIterator.Where , que en realidad combina los predicados (usando Enumerable.CombinePredicates ).

Entonces es más como if(t.IsSomeValue && t.IsSomeOtherValue) .

    
respondido por el sloth 29.10.2013 - 16:43
1

No, no es lo mismo. En su ejemplo, things es un IEnumerable , que en este punto todavía es solo un iterador, no una matriz o lista real. Además, como no se usa things , el ciclo nunca se evalúa. El tipo IEnumerable permite iterar a través de los elementos yield -ed por las instrucciones de Linq y procesarlos con más instrucciones, lo que significa que al final solo tiene un solo bucle.

Pero tan pronto como agrega una instrucción como .ToArray() o .ToList() , está ordenando la creación de una estructura de datos real, poniendo límites a su cadena.

Vea esta pregunta SO relacionada: enlace

    
respondido por el Julien Guertault 29.10.2013 - 16:37

Lea otras preguntas en las etiquetas