¿Importa el orden de las funciones LINQ?

114

Básicamente, como dice la pregunta ... ¿importa el orden de las funciones LINQ en términos de rendimiento ? Obviamente, los resultados tendrían que ser idénticos aún ...

Ejemplo:

myCollection.OrderBy(item => item.CreatedDate).Where(item => item.Code > 3);
myCollection.Where(item => item.Code > 3).OrderBy(item => item.CreatedDate);

Ambos me devuelven los mismos resultados, pero están en un orden LINQ diferente. Me doy cuenta de que reordenar algunos artículos dará como resultado resultados diferentes, y eso no me preocupa. Mi principal preocupación es saber si, al obtener los mismos resultados, los pedidos pueden afectar el rendimiento. Y no solo en las 2 llamadas LINQ que hice (OrderBy, Where), sino en cualquier llamada LINQ.

Miguel
fuente
9
Impresionante pregunta.
Robert S.
Es aún más obvio que la optimización del proveedor importa con un caso más pedante como var query = myCollection.OrderBy(item => item.Code).Where(item => item.Code == 3);.
Mark Hurd
1
Te mereces un voto positivo :), preguntas interesantes. Lo consideraré cuando escriba mi Linq a Entidades en EF.
GibboK
1
@GibboK: tenga cuidado al intentar "optimizar" sus consultas LINQ (consulte la respuesta a continuación). A veces, no terminas optimizando nada. Es mejor utilizar una herramienta de generación de perfiles cuando se intenta la optimización.
myermian

Respuestas:

147

Dependerá del proveedor LINQ en uso. Para LINQ to Objects, eso ciertamente podría marcar una gran diferencia. Supongamos que en realidad tenemos:

var query = myCollection.OrderBy(item => item.CreatedDate)
                        .Where(item => item.Code > 3);

var result = query.Last();

Eso requiere que toda la colección se ordene y luego se filtre. Si tuviéramos un millón de elementos, solo uno de los cuales tuviera un código mayor que 3, estaríamos perdiendo mucho tiempo ordenando resultados que se desecharían.

Compare eso con la operación inversa, filtrando primero:

var query = myCollection.Where(item => item.Code > 3)
                        .OrderBy(item => item.CreatedDate);

var result = query.Last();

Esta vez solo estamos ordenando los resultados filtrados, que en el caso de muestra de "un solo elemento que coincide con el filtro" será mucho más eficiente, tanto en tiempo como en espacio.

También podría marcar la diferencia en si la consulta se ejecuta correctamente o no. Considerar:

var query = myCollection.Where(item => item.Code != 0)
                        .OrderBy(item => 10 / item.Code);

var result = query.Last();

Está bien, sabemos que nunca dividiremos entre 0. Pero si realizamos el orden antes del filtrado, la consulta arrojará una excepción.

Jon Skeet
fuente
2
@Jon Skeet, ¿hay documentación sobre Big-O para cada uno de los proveedores y funciones de LINQ? ¿O se trata simplemente de un caso de "cada expresión es única para la situación"?
Michael
1
@michael: No está muy claramente documentado, pero si lees mi serie de blogs "Edulinq", creo que hablo de ello con un detalle razonable.
Jon Skeet
3
@michael: puedes encontrarlo aquí msmvps.com/blogs/jon_skeet/archive/tags/Edulinq/default.aspx
VoodooChild
3
@gdoron: Para ser honesto, no está muy claro a qué te refieres. Parece que quizás quieras escribir una nueva pregunta. Tenga en cuenta que Queryable no está tratando de interpretar su consulta en absoluto, su trabajo es únicamente preservar su consulta para que otra persona pueda interpretarla. También tenga en cuenta que LINQ to Objects ni siquiera usa árboles de expresión.
Jon Skeet
1
@gdoron: El punto es que ese es el trabajo del proveedor, no el trabajo de Queryable. Y tampoco debería importar cuando se usa Entity Framework. Sin embargo , importa para LINQ to Objects. Pero sí, por supuesto, haga otra pregunta.
Jon Skeet
17

Si.

Pero exactamente cuál es esa diferencia de rendimiento depende de cómo el proveedor LINQ evalúe el árbol de expresión subyacente.

Por ejemplo, su consulta puede ejecutarse más rápido la segunda vez (con la cláusula WHERE primero) para LINQ-to-XML, pero más rápido la primera vez para LINQ-to-SQL.

Para averiguar con precisión cuál es la diferencia de rendimiento, lo más probable es que desee perfilar su aplicación. Sin embargo, como siempre con estas cosas, la optimización prematura no suele merecer el esfuerzo; es posible que descubra que otros problemas además del rendimiento de LINQ son más importantes.

Jeremy McGee
fuente
5

En su ejemplo particular, puede marcar la diferencia en el rendimiento.

Primera consulta: su OrderByllamada debe recorrer en iteración toda la secuencia de origen, incluidos aquellos elementos en los que Codees 3 o menos. La Wherecláusula entonces también tiene que recorrer la totalidad ordenó secuencia.

Segunda consulta: la Wherellamada limita la secuencia a solo aquellos elementos donde Codees mayor que 3. La OrderByllamada sólo necesita atravesar la secuencia reducida devuelta por la Wherellamada.

LukeH
fuente
3

En Linq-To-Objects:

La clasificación es bastante lenta y usa O(n)memoria. Wherepor otro lado, es relativamente rápido y usa memoria constante. Por lo tanto, hacerlo Whereprimero será más rápido y para colecciones grandes significativamente más rápido.

La presión de memoria reducida también puede ser significativa, ya que las asignaciones en el montón de objetos grandes (junto con su colección) son relativamente caras en mi experiencia.

CódigosInChaos
fuente
1

Obviamente, los resultados tendrían que ser idénticos aún ...

Tenga en cuenta que esto no es realmente cierto; en particular, las siguientes dos líneas darán resultados diferentes (para la mayoría de proveedores / conjuntos de datos):

myCollection.OrderBy(o => o).Distinct();
myCollection.Distinct().OrderBy(o => o);
BlueRaja - Danny Pflughoeft
fuente
1
No, lo que quise decir es que los resultados deben ser idénticos para incluso considerar la optimización. No tiene sentido "optimizar" algo y obtener un resultado diferente.
Michael
1

Vale la pena señalar que debe tener cuidado al considerar cómo optimizar una consulta LINQ. Por ejemplo, si usa la versión declarativa de LINQ para hacer lo siguiente:

public class Record
{
    public string Name { get; set; }
    public double Score1 { get; set; }
    public double Score2 { get; set; }
}


var query = from record in Records
            order by ((record.Score1 + record.Score2) / 2) descending
            select new
                   {
                       Name = record.Name,
                       Average = ((record.Score1 + record.Score2) / 2)
                   };

Si, por cualquier motivo, decidiera "optimizar" la consulta almacenando primero el promedio en una variable, no obtendría los resultados deseados:

// The following two queries actually takes up more space and are slower
var query = from record in Records
            let average = ((record.Score1 + record.Score2) / 2)
            order by average descending
            select new
                   {
                       Name = record.Name,
                       Average = average
                   };

var query = from record in Records
            let average = ((record.Score1 + record.Score2) / 2)
            select new
                   {
                       Name = record.Name,
                       Average = average
                   }
            order by average descending;

Sé que no mucha gente usa LINQ declarativo para objetos, pero es un buen motivo para pensar.

myermian
fuente
0

Depende de la relevancia. Suponga que si tiene muy pocos artículos con Código = 3, entonces el próximo pedido funcionará en un conjunto pequeño de colección para obtener el pedido por fecha.

Mientras que si tiene muchos artículos con la misma Fecha de creación, el siguiente pedido funcionará en un conjunto de colecciones más grande para obtener el pedido por fecha.

Entonces, en ambos casos habrá una diferencia en el rendimiento

Pankaj Upadhyay
fuente