Häufig gestellte Fragen zur LINQ-Leistung

70

Ich versuche, LINQ in den Griff zu bekommen. Was mich am meisten stört, ist, dass ich, obwohl ich die Syntax besser verstehe, die Leistung nicht unabsichtlich für die Ausdruckskraft opfern möchte.

Sind sie gute zentralisierte Informationsspeicher oder Bücher für 'Effective LINQ'? Wenn dies nicht der Fall ist, was ist Ihre persönliche Lieblings-Hochleistungs-LINQ-Technik?

Ich beschäftige mich hauptsächlich mit LINQ to Objects, aber alle Vorschläge zu LINQ to SQL und LINQ to XML sind natürlich auch willkommen. Vielen Dank.

Steve Townsend
quelle
2
Nach meiner persönlichen Erfahrung entspricht LINQ genau der Geschwindigkeit, die Sie selbst schreiben würden. In vielen Fällen ist es aufgrund der verzögerten Auswertung (und der verrückten Optimierungen, die das .NET-Team bereits für Sie vorgenommen hat) sogar noch schneller.
Travis Gockel
1
Ich weiß nicht, in welcher Umgebung Sie arbeiten, aber ich arbeite an Unternehmensanwendungen, bei denen die Leistung entscheidend ist. Jede einzelne Methode wird regelmäßig auf ihre Leistung überprüft, und LINQ ist strengstens verboten. Die Verwendung von LINQ ist strafbar, wenn eine Woche lang jeden Tag auf Anfrage Tee / Kaffee für alle im Büro zubereitet werden muss.
3
@ Rex M - genau aus diesem Grund habe ich die Frage gestellt. Es fällt mir schwer, Fälle aufzuspüren, in denen sich LINQ gegenüber handcodierten Schleifen verbessert, aber viele Gegenbeispiele, wenn LINQ auf Hochtouren bläst. Wenn wir unwissend sind, erziehen Sie uns.
Steve Townsend
1
@Steve wie unterscheidet sich das von allem anderen? Sie sollten wissen, dass verschachtelte for-Schleifen O (Nx) sind, und Sie sollten wissen, dass LINQ-Streams über yield(und somit O (N)) resultieren . Ich denke, Sie projizieren mehr Schwierigkeiten als tatsächlich vorhanden sind.
Rex M
2
@ Rex M - vielleicht auch. Ich frage mich nur, wie viele der sexy LINQ-Antworten, die ich hier sehe, optimal sind. Die Ausdruckskraft ist verführerisch. Danke für deine Gedanken.
Steve Townsend

Antworten:

63

Wenn Sie nur verstehen, was LINQ intern tut, sollten Sie genügend Informationen erhalten, um zu wissen, ob Sie einen Leistungseinbruch erleiden.

Hier ist ein einfaches Beispiel, in dem LINQ die Leistung verbessert. Betrachten Sie diesen typischen Ansatz der alten Schule:

List<Foo> foos = GetSomeFoos();
List<Foo> filteredFoos = new List<Foo>();
foreach(Foo foo in foos)
{
    if(foo.SomeProperty == "somevalue")
    {
        filteredFoos.Add(foo);
    }
}
myRepeater.DataSource = filteredFoos;
myRepeater.DataBind();

Der obige Code wird also zweimal durchlaufen und einen zweiten Container für die gefilterten Werte zuweisen. Was für eine Verschwendung! Vergleichen mit:

var foos = GetSomeFoos();
var filteredFoos = foos.Where(foo => foo.SomeProperty == "somevalue");
myRepeater.DataSource = filteredFoos;
myRepeater.DataBind();

Dies wird nur einmal wiederholt (wenn der Repeater gebunden ist). Es wird immer nur der Originalbehälter verwendet. filteredFoosist nur ein Zwischenzähler. Und wenn Sie sich aus irgendeinem Grund entscheiden, den Repeater später nicht mehr zu binden, wird nichts verschwendet. Sie iterieren oder bewerten nicht einmal.

Wenn Sie in sehr komplexe Sequenzmanipulationen geraten, können Sie möglicherweise viel gewinnen, indem Sie die inhärente Verwendung von Verkettung und verzögerter Auswertung durch LINQ nutzen. Wie bei allem geht es auch hier nur darum zu verstehen, was es tatsächlich tut.

Rex M.
quelle
2
Danke, dies ist ein Beispiel für genau das, was ich mir erhofft hatte. Ich wünschte nur, es wäre einfacher, anhand von Beispielen zu lernen als Versuch und Irrtum oder eine A-priori- Analyse.
Steve Townsend
+1 für die zweimal iterierte Stichprobe. Gut zu beachten, dass die gefilterte Liste auch Speicher verbraucht, was den GC etwas belastet.
Devgeezer
8
Ich verstehe nicht, wie das erste Beispiel zweimal wiederholt wird. Ist dies ein Effekt von 'myRepeater', wenn Sie 'DataBind ()' darauf aufrufen?
AR
@AR überlegt, wie ein Repeater intern arbeiten müsste - er hat eine Artikelvorlage und eine Datenquelle - er muss über die Quelle foreach und jedes Element an eine Kopie der Artikelvorlage binden.
Rex M
8
Ich kann nicht verstehen, warum die LINQ-Implementierung schneller sein sollte. Zumindest nicht unter normalen Umständen, wenn der Repeater wirklich aufgezählt ist. Überprüfen Sie die LINQ-Quellen und Sie werden sehen ... Mit LINQ speichern Sie eine Zuordnung, führen jedoch viel mehr Code aus (mehr Methoden, mehr Überprüfungen) und dieser Code wird weniger effizient ausgeführt (gemischt mit dem Aufrufercode).
Jan Slodicka
96

Linq hat als integrierte Technologie Leistungsvor- und -nachteile. Dem Code hinter den Erweiterungsmethoden wurde vom .NET-Team erhebliche Aufmerksamkeit geschenkt, und seine Fähigkeit, eine verzögerte Auswertung bereitzustellen, bedeutet, dass die Kosten für die Durchführung der meisten Manipulationen an einer Gruppe von Objekten auf den größeren Algorithmus verteilt sind, der die manipulierte Gruppe erfordert . Es gibt jedoch einige Dinge, die Sie wissen müssen, um die Leistung Ihres Codes zu beeinträchtigen oder zu beeinträchtigen.

In erster Linie spart Linq Ihrem Programm nicht auf magische Weise die Zeit oder den Speicher, die für die Ausführung einer Operation erforderlich sind. Dies kann diese Vorgänge nur verzögern, bis sie unbedingt benötigt werden. OrderBy () führt einen QuickSort durch, der genauso lange dauert, als hätten Sie Ihren eigenen QuickSorter geschrieben oder List.Sort () zum richtigen Zeitpunkt verwendet. Denken Sie also immer daran, was Sie von Linq verlangen, wenn Sie Abfragen schreiben. Wenn eine Manipulation nicht erforderlich ist, versuchen Sie, die Abfrage- oder Methodenkette neu zu strukturieren, um dies zu vermeiden.

Aus dem gleichen Grund erfordern bestimmte Operationen (Sortieren, Gruppieren, Aggregieren) die Kenntnis der gesamten Menge, auf die sie einwirken. Das allerletzte Element in einer Reihe könnte das erste sein, das die Operation von ihrem Iterator zurückgeben muss. Da Linq-Operationen ihre aufzählbare Quelle nicht ändern sollten, aber viele der von ihnen verwendeten Algorithmen (dh In-Place-Sortierungen), werden diese Operationen nicht nur ausgewertet, sondern die gesamte Aufzählung in eine konkrete, endliche Struktur kopiert , die Operation ausführen und durch sie nachgeben. Wenn Sie also OrderBy () in einer Anweisung verwenden und nach einem Element aus dem Endergebnis fragen, wird ALLES, was die ihm gegebene IEnumerable erzeugen kann, ausgewertet, als Array im Speicher gespeichert, sortiert und dann ein Element an a zurückgegeben Zeit. Die Moral ist,

Schließlich erhöhen Linq-Methoden die Größe des Aufrufstapels und den Speicherbedarf Ihres Systems drastisch. Jede Operation, die den gesamten Satz kennen muss, behält den gesamten Quellensatz im Speicher, bis das letzte Element iteriert wurde, und die Auswertung jedes Elements umfasst einen Aufrufstapel, der mindestens doppelt so tief ist wie die Anzahl der Methoden in Ihrer Kette oder Ihren Klauseln in Ihrer Inline-Anweisung (ein Aufruf von MoveNext () jedes Iterators oder GetEnumerator plus mindestens ein Aufruf von jedem Lambda auf dem Weg). Dies führt einfach zu einem größeren, langsameren Algorithmus als ein intelligent entwickelter Inline-Algorithmus, der dieselben Manipulationen ausführt. Der Hauptvorteil von Linq ist die Einfachheit des Codes. Das Erstellen und Sortieren eines Wörterbuchs mit Listen von Gruppenwerten ist kein leicht verständlicher Code (vertrauen Sie mir). Mikrooptimierungen können dies weiter verschleiern. Wenn die Leistung Ihr Hauptanliegen ist, verwenden Sie Linq nicht. Dies erhöht den Zeitaufwand um ca. 10% und den Speicheraufwand für die Bearbeitung einer Liste selbst um ein Vielfaches. Die Wartbarkeit ist jedoch in der Regel das Hauptanliegen der Entwickler, und Linq hilft dort ENDGÜLTIG.

Zum Performance-Kick: Wenn die Leistung Ihres Algorithmus die heilige, kompromisslose erste Priorität ist, würden Sie in einer nicht verwalteten Sprache wie C ++ programmieren. .NET wird aufgrund seiner verwalteten Laufzeitumgebung mit nativer JIT-Kompilierung, verwaltetem Speicher und zusätzlichen Systemthreads viel langsamer. Ich würde eine Philosophie annehmen, dass es "gut genug" ist; Linq kann von Natur aus Verlangsamungen einführen, aber wenn Sie den Unterschied nicht erkennen können und Ihr Kunde den Unterschied nicht erkennen kann, gibt es für alle praktischen Zwecke keinen Unterschied. "Vorzeitige Optimierung ist die Wurzel allen Übels"; Lassen Sie es funktionieren, und suchen Sie dann nach Möglichkeiten, um es leistungsfähiger zu machen, bis Sie und Ihr Kunde sich einig sind, dass es gut genug ist. Es könnte immer "besser" sein, aber es sei denn, Sie möchten Maschinencode von Hand verpacken,

KeithS
quelle
1
"Wenn Leistung Ihr Hauptanliegen ist, verwenden Sie Linq nicht" - Was ist mit der Webentwicklung? Dort könnte die Leistung ein Hauptanliegen sein, da Sie erwarten könnten, dass Tausende von Benutzern gleichzeitig Aktionen ausführen. Würden Sie also versuchen, Linq bei der Webentwicklung zu vermeiden?
BornToCode
6
Wellll ... Ja, die Leistung ist ein Problem, aber ein Großteil des Mehrbenutzeraspekts wird für Sie von der Architektur der ASP.NET-Laufzeit und des Lebenszyklus übernommen. Darüber hinaus werden Laufzeitleistungsprobleme normalerweise durch Netzwerkbandbreiten- / Latenzprobleme in den Schatten gestellt, die den größten Teil der Verzögerung eines Postbacks ausmachen. Sie sollten die serverseitige Arbeit immer noch so schnell wie möglich erledigen, aber wenn meine Website unter realen Verkehrslasten nicht schnell genug reagiert, wäre eine relativ gut strukturierte Linq-Abfrage nicht mein erster Verdächtiger.
KeithS
4

Es gibt verschiedene Faktoren, die die Leistung beeinflussen.

Oft bietet die Entwicklung einer Lösung mit LINQ eine recht vernünftige Leistung, da das System einen Ausdrucksbaum erstellen kann, um die Abfrage darzustellen, ohne die Abfrage tatsächlich auszuführen, während es diese erstellt. Nur wenn Sie die Ergebnisse durchlaufen, wird dieser Ausdrucksbaum zum Generieren und Ausführen einer Abfrage verwendet.

In Bezug auf die absolute Effizienz kann es bei vordefinierten gespeicherten Prozeduren zu Leistungseinbußen kommen. Im Allgemeinen besteht der Ansatz jedoch darin, eine Lösung mit einem System zu entwickeln, das eine angemessene Leistung bietet (z. B. LINQ), und sich nicht um ein paar Prozent Verlust zu sorgen der Leistung. Wenn eine Abfrage dann langsam ausgeführt wird, sehen Sie sich möglicherweise die Optimierung an.

Die Realität ist, dass die meisten Abfragen nicht das geringste Problem haben werden, über LINQ erledigt zu werden. Die andere Tatsache ist, dass wenn Ihre Abfrage langsam ausgeführt wird, es wahrscheinlich wahrscheinlicher ist, dass Probleme mit der Indizierung, Struktur usw. auftreten als mit der Abfrage selbst. Selbst wenn Sie nach Optimierungen suchen, berühren Sie häufig nicht den LINQ, sondern nur den Datenbankstruktur, gegen die es arbeitet.

Wenn beim Umgang mit XML ein Dokument geladen und in den Speicher analysiert wird (wie alles, was auf dem DOM-Modell oder einem XmlDocument oder was auch immer basiert), wird der Speicher stärker beansprucht als bei Systemen, bei denen Ereignisse ausgelöst werden Geben Sie an, dass Sie ein Start- oder End-Tag suchen, aber keine vollständige In-Memory-Version des Dokuments erstellen (z. B. SAX oder XmlReader). Der Nachteil ist, dass die ereignisbasierte Verarbeitung im Allgemeinen etwas komplexer ist. Auch hier gibt es bei den meisten Dokumenten kein Problem - die meisten Systeme verfügen über mehrere GB RAM, sodass das Aufnehmen einiger MB für ein einzelnes XML-Dokument kein Problem darstellt (und Sie häufig einen großen Satz von XML-Dokumenten zumindest etwas verarbeiten der Reihe nach). Nur wenn Sie eine riesige XML-Datei haben, die 100 MB aufnehmen würde, machen Sie sich Sorgen über die jeweilige Auswahl.

Denken Sie daran, dass Sie mit LINQ auch über speicherinterne Listen usw. iterieren können. In einigen Situationen (z. B. wenn Sie eine Reihe von Ergebnissen in einer Funktion immer wieder verwenden) können Sie .ToList verwenden oder .ToArray, um die Ergebnisse zurückzugeben. Manchmal kann dies nützlich sein, obwohl Sie im Allgemeinen versuchen möchten, die Abfrage der Datenbank eher im Arbeitsspeicher zu verwenden.

Bei persönlichen Favoriten - NHibernate LINQ - handelt es sich um ein objektrelationales Zuordnungstool, mit dem Sie Klassen definieren, Zuordnungsdetails definieren und dann die Datenbank aus Ihren Klassen generieren können, und nicht umgekehrt. Die LINQ-Unterstützung ist hübsch gut (sicherlich besser als solche wie SubSonic).

David Burton
quelle
1

In Linq to SQL müssen Sie sich nicht so sehr um die Leistung kümmern. Sie können alle Ihre Aussagen so verketten, wie Sie es für am besten lesbar halten. Linq übersetzt am Ende nur alle Ihre Anweisungen in eine SQL-Anweisung, die erst am Ende aufgerufen / ausgeführt wird (wie beim Aufrufen von a.ToList()

a varkann diese Anweisung enthalten, ohne sie auszuführen, wenn Sie verschiedene zusätzliche Anweisungen unter verschiedenen Bedingungen anwenden möchten. Die Ausführung am Ende erfolgt nur, wenn Sie Ihre Anweisungen in ein Ergebnis wie ein Objekt oder eine Liste von Objekten übersetzen möchten.

Stefanvds
quelle
2
Dies gilt nur für LINQ to SQL.
Nlawalker
Er hat. Siehe den letzten Satz der ersten Revision.
Rex M
1
-1. Manchmal übersetzt LinqToSql Ihre Abfrage in n + 1 Anweisungen. Manchmal ist die erzeugte Abfrage nicht optimal. LinqToSql-Abfragen erfordern eine Analyse der Datenbankleistung, die sich nicht von anderen Abfragen aus anderen Quellen unterscheidet.
Amy B
1

Es gibt ein Codeplex-Projekt namens i4o, das ich vor einiger Zeit verwendet habe, um die Leistung von Linq to Objects in Fällen zu verbessern, in denen Sie Gleichheitsvergleiche durchführen, z

from p in People 
where p.Age == 21 
select p;

http://i4o.codeplex.com/ Ich habe es nicht mit .Net 4 getestet, kann also nicht sicher sagen, dass es immer noch funktioniert, aber es lohnt sich, es sich anzusehen. Damit es seine Magie entfaltet, müssen Sie Ihre Klasse meist nur mit einigen Attributen dekorieren, um anzugeben, welche Eigenschaft indiziert werden soll. Wenn ich es vorher benutzt habe, funktioniert es allerdings nur mit Gleichheitsvergleichen.

theburningmonk
quelle
Ich habe festgestellt, dass es im Allgemeinen besser ist, die Indizes selbst zu erstellen, wenn ich etwas mit LINQ mache, das Indizes benötigt, um es zu beschleunigen, da a) ich sie möglicherweise für andere Zwecke möchte und b) ich kontrolliere, wie die Indizes verwaltet werden . Dies ermöglicht mir auch, viel mehr vollständige Indizes zu erstellen
RobV