Die LINQ-Abfrage ist langsam

8

Während der Profilerstellung der Anwendung habe ich festgestellt, dass die Funktion zum Überprüfen der Musterübereinstimmung sehr langsam ist. Es wird mit LINQ geschrieben. Die einfache Ersetzung dieses LINQ-Ausdrucks durch einen Loop macht wirklich einen großen Unterschied. Was ist es? Ist LINQ wirklich so eine schlechte Sache und funktioniert so langsam oder ich missverstehe etwas?

%Vor%

Version 2 mit LINQ (vorgeschlagen von Resharper)

%Vor%

Version 3 mit LINQ

%Vor%

Version 4 mit Lambda

%Vor%

und hier ist die Verwendung mit großem Puffer

%Vor%

Der Aufruf von PatternMatch2 oder PatternMatch3 ist phänomenal langsam. Es dauert etwa 8 Sekunden für PatternMatch3 und etwa 4 Sekunden für PatternMatch2 , während der Aufruf von PatternMatch1 etwa 0,6 Sekunden dauert. Soweit ich es verstehe, ist es der gleiche Code und ich sehe keinen Unterschied. Irgendwelche Ideen?

    
axe 10.10.2012, 12:51
quelle

4 Antworten

6

Nun nehmen wir den Where-Operator.

Es ist die Implementierung fast (*) wie:

%Vor%

Dies bedeutet, dass für jede Schleife über IEnumerable ein Iterator-Objekt erstellt wird - beachten Sie, dass SIZE - n diese Zuordnungen haben, weil Sie diese vielen LINQ-Abfragen durchführen.

Dann haben Sie für jedes Zeichen im Muster:

  1. Ein Funktionsaufruf an den Enumerator
  2. Ein Funktionsaufruf an das Prädikat

Der zweite ist ein Anruf an einen Delegierten, der etwa doppelt so viel kostet wie ein typischer virtueller Anruf (wobei in der ersten Version keine zusätzlichen Anrufe benötigt werden, um das Array zu desindizieren.

)

Wenn Sie wirklich brachiale Leistung wollen, möchten Sie wahrscheinlich so viel wie "älteren Stil" Code zu bekommen. In diesem Fall würde ich diese Methode in Methode 1 sogar durch eine Ebene ersetzen (zumindest, wenn sie nicht zur Optimierung dient, macht sie sie lesbarer, da Sie den Index sowieso im Auge behalten).

Es ist auch in diesem Fall besser lesbar, und es zeigt, dass Resharper-Vorschläge manchmal diskutabel sind.

(*) Ich sagte fast, weil es eine Proxy-Methode verwendet, um zu überprüfen, dass die Eingabe-Enumerable nicht null ist und die Ausnahme vor dem Zeitpunkt der Auflistung aufzugeben ist - es ist ein kleines Detail, das nicht ungültig macht, was ich zuvor geschrieben habe , hier auf Vollständigkeit hinweisen.

    
Marco Mp 10.10.2012, 13:06
quelle
7

Mark Byers und Marco Mp haben recht, was die zusätzlichen Anrufe angeht. Es gibt jedoch noch einen weiteren Grund: viele Objektzuordnungen aufgrund einer Schließung.

Der Compiler erstellt bei jeder Iteration ein neues Objekt und speichert die aktuellen Werte von buffer , position und i , die das Prädikat verwenden kann. Hier ist ein dotTrace-Snapshot von PatternMatch2 , der dies zeigt. <>c_DisplayClass1 ist die vom Compiler generierte Klasse.

(Beachten Sie, dass die Zahlen sehr groß sind, da ich Tracing-Profiling verwende, was Overhead hinzufügt. Es ist jedoch der gleiche Overhead pro Aufruf, also sind die Prozentsätze insgesamt richtig).

    
Julien Lebosquain 10.10.2012 13:14
quelle
6

Der Unterschied ist, dass die LINQ-Version zusätzliche Funktionsaufrufe hat. Intern wird in LINQ Ihre Lambda-Funktion in einer Schleife aufgerufen.

Während der zusätzliche Aufruf möglicherweise durch den JIT-Compiler optimiert wurde Es ist nicht garantiert und es kann einen kleinen Overhead hinzufügen. In den meisten Fällen spielt der zusätzliche Overhead keine Rolle, aber da Ihre Funktion sehr einfach ist und extrem oft aufgerufen wird, kann sich sogar ein kleiner Overhead schnell addieren. Nach meiner Erfahrung ist der LINQ-Code im Allgemeinen etwas langsamer als die entsprechenden for -Schleifen. Das ist der Leistungspreis, den Sie oft für eine höhere Syntax zahlen.

In dieser Situation würde ich empfehlen, bei der expliziten Schleife zu bleiben.

Während Sie diesen Codeabschnitt optimieren, sollten Sie auch einen effizienteren Suchalgorithmus in Betracht ziehen. Ihr Algorithmus ist der schlechteste Fall O (n * m), aber bessere Algorithmen existieren .

    
Mark Byers 10.10.2012 12:54
quelle
1

LINQs Hauptziel bei Sammlungen ist seine Einfachheit. Wenn Sie Leistung wünschen, sollten Sie LINQ vollständig vermeiden. Um ein Array aufzuzählen, inkrementieren Sie nur eine Indexvariable, während LINQ ein komplettes Enumeratorobjekt einrichten muss.

    
Wormbo 10.10.2012 12:56
quelle

Tags und Links