Ist die Reihenfolge der LINQ-Funktionen wichtig?


114

Grundsätzlich ist, wie in der Frage angegeben, die Reihenfolge der LINQ-Funktionen für die Leistung von Bedeutung ? Offensichtlich müssten die Ergebnisse noch identisch sein ...

Beispiel:

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

Beide geben mir die gleichen Ergebnisse zurück, befinden sich jedoch in einer anderen LINQ-Reihenfolge. Mir ist klar, dass das Nachbestellen einiger Artikel zu unterschiedlichen Ergebnissen führt, und ich mache mir darüber keine Sorgen. Mein Hauptanliegen ist es zu wissen, ob die Bestellung die Leistung beeinträchtigen kann, um die gleichen Ergebnisse zu erzielen. Und das nicht nur bei den 2 LINQ-Anrufen (OrderBy, Where), sondern bei allen LINQ-Anrufen.


9
Tolle Frage.
Robert S.

Es ist noch offensichtlicher, dass die Optimierung des Anbieters bei einem pedantischeren Fall wie z var query = myCollection.OrderBy(item => item.Code).Where(item => item.Code == 3);.
Mark Hurd

1
Sie verdienen eine Up Vote :), interessante Fragen. Ich werde es berücksichtigen, wenn ich meine Linq an Entities in EF schreibe.
GibboK

1
@ GibboK: Seien Sie vorsichtig, wenn Sie versuchen, Ihre LINQ-Abfragen zu "optimieren" (siehe Antwort unten). Manchmal optimiert man eigentlich nichts. Verwenden Sie am besten ein Profiler-Tool, wenn Sie eine Optimierung versuchen.
Myermian

Antworten:


147

Dies hängt vom verwendeten LINQ-Anbieter ab. Für LINQ to Objects könnte dies sicherlich einen großen Unterschied machen. Angenommen, wir haben tatsächlich:

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

var result = query.Last();

Dazu muss die gesamte Sammlung sortiert und dann gefiltert werden. Wenn wir eine Million Artikel hätten, von denen nur einer einen Code größer als 3 hätte, würden wir viel Zeit damit verschwenden, Ergebnisse zu bestellen, die weggeworfen würden.

Vergleichen Sie das mit der umgekehrten Operation, indem Sie zuerst filtern:

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

var result = query.Last();

Dieses Mal bestellen wir nur die gefilterten Ergebnisse, was im Beispielfall "nur ein einziges Element, das zum Filter passt" viel effizienter ist - sowohl zeitlich als auch räumlich.

Es ist auch könnte einen Unterschied, ob die Abfrage ausgeführt werden korrekt ist oder nicht. Erwägen:

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

var result = query.Last();

Das ist in Ordnung - wir wissen, dass wir niemals durch 0 teilen werden. Wenn wir jedoch die Reihenfolge vor dem Filtern ausführen, löst die Abfrage eine Ausnahme aus.


2
@ Jon Skeet, Gibt es eine Dokumentation zum Big-O für jeden der LINQ-Anbieter und -Funktionen? Oder ist dies nur ein Fall von "jeder Ausdruck ist einzigartig für die Situation".
Michael

1
@michael: Es ist nicht sehr klar dokumentiert, aber wenn Sie meine "Edulinq" -Blog-Serie lesen, denke ich, dass ich ziemlich ausführlich darüber spreche.
Jon Skeet


3
@gdoron: Es ist nicht wirklich klar, was du meinst, um ehrlich zu sein. Klingt so, als ob Sie eine neue Frage schreiben möchten. Denken Sie daran, dass Queryable überhaupt nicht versucht, Ihre Abfrage zu interpretieren. Es besteht lediglich darin, Ihre Abfrage beizubehalten , damit etwas anderes sie interpretieren kann. Beachten Sie auch, dass LINQ to Objects nicht einmal Ausdrucksbäume verwendet.
Jon Skeet

1
@gdoron: Der Punkt ist, dass dies der Job des Anbieters ist, nicht der Job von Queryable. Auch bei der Verwendung von Entity Framework sollte dies keine Rolle spielen. Es tut Angelegenheit für LINQ to Objects though. Aber ja, stellen Sie auf jeden Fall eine andere Frage.
Jon Skeet

17

Ja.

Was genau dieser Leistungsunterschied ist, hängt jedoch davon ab, wie der zugrunde liegende Ausdrucksbaum vom LINQ-Anbieter bewertet wird.

Beispielsweise kann Ihre Abfrage beim zweiten Mal (mit der WHERE-Klausel zuerst) für LINQ-to-XML schneller ausgeführt werden, beim ersten Mal jedoch schneller für LINQ-to-SQL.

Um genau herauszufinden, was der Leistungsunterschied ist, möchten Sie höchstwahrscheinlich Ihre Anwendung profilieren. Wie immer bei solchen Dingen lohnt sich eine vorzeitige Optimierung jedoch normalerweise nicht. Möglicherweise sind andere Probleme als die LINQ-Leistung wichtiger.


5

In Ihrem speziellen Beispiel kann es dies die Leistung beeinflussen.

Erste Abfrage: Ihr OrderByAnruf muss die gesamte Quellsequenz durchlaufen , einschließlich der Elemente mit Code3 oder weniger. Die WhereKlausel muss dann auch die gesamte geordnete Sequenz iterieren .

Zweite Abfrage: Der WhereAnruf beschränkt die Sequenz nur auf die Elemente, die Codegrößer als 3 sind. Der OrderByAnruf muss dann nur die reduzierte Sequenz durchlaufen, die vom WhereAnruf zurückgegeben wird.


3

In Linq-To-Objects:

Das Sortieren ist ziemlich langsam und verwendet O(n)Speicher. WhereAndererseits ist es relativ schnell und verwendet konstanten Speicher. Also mach esWhere erste Mal geht es also schneller und bei großen Sammlungen deutlich schneller.

Der reduzierte Speicherdruck kann ebenfalls erheblich sein, da Zuordnungen auf dem großen Objekthaufen (zusammen mit ihrer Sammlung) meiner Erfahrung nach relativ teuer sind.


1

Offensichtlich müssten die Ergebnisse noch identisch sein ...

Beachten Sie, dass dies nicht der Fall ist. Insbesondere die folgenden beiden Zeilen führen zu unterschiedlichen Ergebnissen (für die meisten Anbieter / Datensätze):

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

1
Nein, ich meinte, dass die Ergebnisse identisch sein sollten, um überhaupt eine Optimierung in Betracht zu ziehen. Es macht keinen Sinn, etwas zu "optimieren" und ein anderes Ergebnis zu erzielen.
Michael

1

Es ist erwähnenswert, dass Sie sollten vorsichtig sein , wenn man bedenkt , wie eine LINQ - Abfrage zu optimieren. Wenn Sie beispielsweise die deklarative Version von LINQ verwenden, um Folgendes zu tun:

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

Wenn Sie aus irgendeinem Grund beschlossen haben, die Abfrage zu "optimieren", indem Sie den Durchschnitt zuerst in einer Variablen speichern, erhalten Sie nicht die gewünschten Ergebnisse:

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

Ich weiß, dass nicht viele Leute deklarativen LINQ für Objekte verwenden, aber es ist ein guter Denkanstoß.


0

Das hängt von der Relevanz ab. Angenommen, Sie haben nur sehr wenige Artikel mit Code = 3, dann funktioniert die nächste Bestellung mit einem kleinen Sammlungssatz, um die Bestellung nach Datum zu erhalten.

Wenn Sie hingegen viele Artikel mit demselben Erstellungsdatum haben, funktioniert die nächste Bestellung mit einem größeren Sammlungssatz, um die Bestellung nach Datum zu erhalten.

In beiden Fällen gibt es also einen Leistungsunterschied

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.