L'ordine delle funzioni LINQ è importante?


114

Fondamentalmente, come afferma la domanda ... l'ordine delle funzioni LINQ è importante in termini di prestazioni ? Ovviamente i risultati dovrebbero essere ancora identici ...

Esempio:

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

Entrambi restituiscono gli stessi risultati, ma sono in un ordine LINQ diverso. Mi rendo conto che il riordino di alcuni articoli comporterà risultati diversi e non sono preoccupato per quelli. La mia principale preoccupazione è sapere se, ottenendo gli stessi risultati, l'ordinazione può influire sulle prestazioni. E non solo sulle 2 chiamate LINQ che ho effettuato (OrderBy, Where), ma su tutte le chiamate LINQ.


9
Domanda fantastica.
Robert S.

È ancora più ovvio che l'ottimizzazione del provider è importante con un caso più pedante come var query = myCollection.OrderBy(item => item.Code).Where(item => item.Code == 3);.
Mark Hurd

1
Ti meriti un voto positivo :), domande interessanti. Lo terrò in considerazione quando scriverò il mio Linq a Entities in EF.
GibboK

1
@ GibboK: fai attenzione quando cerchi di "ottimizzare" le tue query LINQ (vedi risposta sotto). A volte non si finisce effettivamente per ottimizzare nulla. È meglio usare uno strumento di profilazione quando si tenta di ottimizzare.
myermian

Risposte:


147

Dipenderà dal provider LINQ in uso. Per LINQ to Objects, ciò potrebbe sicuramente fare un'enorme differenza. Supponiamo di avere effettivamente:

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

var result = query.Last();

Ciò richiede che l' intera raccolta venga ordinata e quindi filtrata. Se avessimo un milione di elementi, solo uno dei quali aveva un codice maggiore di 3, perderemmo molto tempo per ordinare risultati che verrebbero buttati via.

Confrontalo con l'operazione inversa, filtrando prima:

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

var result = query.Last();

Questa volta stiamo ordinando solo i risultati filtrati, che nel caso campione di "solo un singolo elemento corrispondente al filtro" saranno molto più efficienti, sia nel tempo che nello spazio.

Inoltre potrebbe fare la differenza in se la query viene eseguita correttamente o meno. Tener conto di:

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

var result = query.Last();

Va bene - sappiamo che non divideremo mai per 0. Ma se eseguiamo l'ordinamento prima del filtro, la query genererà un'eccezione.


2
@ Jon Skeet, esiste la documentazione sul Big-O per ciascuno dei provider e delle funzioni LINQ? O è semplicemente un caso di "ogni espressione è unica per la situazione".
michael

1
@michael: Non è documentato molto chiaramente, ma se leggi la mia serie di blog "Edulinq" penso di parlarne in modo ragionevole.
Jon Skeet,


3
@gdoron: non è proprio chiaro cosa intendi, ad essere onesti. Sembra che tu voglia scrivere una nuova domanda. Tieni presente che Queryable non sta cercando di interpretare affatto la tua query: il suo compito è esclusivamente quello di preservare la tua query in modo che qualcos'altro possa interpretarla. Si noti inoltre che LINQ to Objects non utilizza nemmeno gli alberi delle espressioni.
Jon Skeet

1
@gdoron: il punto è che è il lavoro del provider, non il lavoro di Queryable. E non dovrebbe importare nemmeno quando si utilizza Entity Framework. Si fa materia per LINQ to Objects però. Ma sì, in ogni caso fai un'altra domanda.
Jon Skeet

17

Sì.

Ma esattamente che cosa questa differenza di prestazioni è dipende da come l'albero di espressione sottostante viene valutato dal fornitore di LINQ.

Ad esempio, la tua query potrebbe essere eseguita più velocemente la seconda volta (con la clausola WHERE prima) per LINQ-to-XML, ma più veloce la prima volta per LINQ-to-SQL.

Per scoprire esattamente qual è la differenza di prestazioni, molto probabilmente vorrai profilare la tua applicazione. Come sempre con queste cose, tuttavia, l'ottimizzazione prematura di solito non vale lo sforzo: potresti scoprire che problemi diversi dalle prestazioni di LINQ sono più importanti.


5

Nel tuo esempio particolare può fare la differenza per le prestazioni.

Prima query: la OrderBychiamata deve scorrere l' intera sequenza di origine, inclusi gli elementi in cui Codeè 3 o meno. La Whereclausola quindi deve anche iterare l' intera sequenza ordinata.

Seconda query: la Wherechiamata limita la sequenza solo agli elementi in cui Codeè maggiore di 3. La OrderBychiamata quindi deve solo attraversare la sequenza ridotta restituita dalla Wherechiamata.


3

In Linq-To-Objects:

L'ordinamento è piuttosto lento e utilizza la O(n)memoria. Whered'altra parte è relativamente veloce e utilizza una memoria costante. Quindi fare il Whereprimo sarà più veloce e per grandi raccolte molto più velocemente.

Anche la ridotta pressione della memoria può essere significativa, poiché le allocazioni sull'heap di oggetti di grandi dimensioni (insieme alla loro raccolta) sono relativamente costose nella mia esperienza.


1

Ovviamente i risultati dovrebbero essere ancora identici ...

Nota che questo non è effettivamente vero - in particolare, le seguenti due righe daranno risultati diversi (per la maggior parte dei provider / set di dati):

myCollection.OrderBy(o => o).Distinct();
myCollection.Distinct().OrderBy(o => o);

1
No, quello che volevo dire era che i risultati dovrebbero essere identici anche per considerare l'ottimizzazione. Non ha senso "ottimizzare" qualcosa e ottenere un risultato diverso.
michael

1

Vale la pena notare che è necessario prestare attenzione quando si considera come ottimizzare una query LINQ. Ad esempio, se usi la versione dichiarativa di LINQ per eseguire le operazioni seguenti:

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

Se, per qualsiasi motivo, decidessi di "ottimizzare" la query memorizzando prima la media in una variabile, non otterrai i risultati desiderati:

// 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;

So che non molte persone usano LINQ dichiarativo per gli oggetti, ma è un buon spunto di riflessione.


0

Dipende dalla rilevanza. Supponiamo che se hai pochissimi articoli con Code = 3, l'ordine successivo funzionerà su un piccolo set di raccolta per ottenere l'ordine per data.

Mentre se hai molti articoli con la stessa CreatedDate, l'ordine successivo funzionerà su un set di raccolta più ampio per ottenere l'ordine per data.

Quindi, in entrambi i casi ci sarà una differenza nelle prestazioni

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.