Wird in einem Linq-Prädikat der Compiler einen "skalaren" Aufruf für Enumerable.Min () optimieren oder wird er für jedes Element aufgerufen?

8

Ich habe gerade die Frage " Unteranfrage mit Lambda-Ausdruck " angeschaut und mich über den Compiler erkundigt Optimierung von Linq Prädikaten.

Angenommen, ich hätte eine List<string> namens names , und ich suchte nach den Elementen mit der kürzesten Stringlänge. Also haben wir die Abfrage names.Where(x => x.Length == names.Min(y => y.Length)) (aus der oben genannten Frage). Einfach genug.

Nun wissen wir, dass es die C # -Spezifikation nicht erlaubt, eine Auflistung zu ändern, während sie aufgelistet wird. Daher glaube ich, dass es technisch sicher ist anzunehmen, dass der obige Aufruf an Min() immer den gleichen Wert für jeden Aufruf zurückgibt.

Aber meine Hypothese ist, dass der Compiler wirklich keine Möglichkeit hat zu wissen, was das Lambda in der Enumerable.Min Erweiterungsmethode zurückgibt. Da könnten wir zum Beispiel:

%Vor%

Das würde bedeuten, dass die in Frage stehende Anfrage wirklich O (n²) ist - das Ergebnis von Min() wird für jede Iteration berechnet. Und um die gewünschte O (n) Implementierung zu erhalten, müssten Sie explizit sein:

%Vor%

Ist meine Hypothese korrekt, oder gibt es etwas Besonderes an Linq oder der C # -Spezifikation, die es dem Compiler ermöglicht, in das Lambda zu schauen und diesen Aufruf für Min() ?

zu optimieren?

@spender ist absolut richtig. Betrachten Sie das folgende Snippet:

%Vor%

Dies wird nur "r" und nicht "q" zurückgeben, weil der alte Verweis auf names iteriert wird ( foreach x ), Der Aufruf von Min nach der ersten Iteration wird tatsächlich mit der neuen Instanz von names aufgerufen. Aber ein Mensch, der die Abfrage oben in der Frage betrachtet, kann mit Sicherheit sagen, dass nichts modifiziert wird. Meine Frage steht also immer noch: Ist der Compiler intelligent genug, um das zu sehen?

    
lc. 14.06.2014, 11:51
quelle

2 Antworten

7
  

hat sich über die Compiler-Optimierung von Linq-Prädikaten gewundert.

Der C # -Compiler weiß nicht, wie die BCL-Typen implementiert sind. Es kann sich die Baugruppen ansehen, auf die Sie verweisen, die sich jedoch jederzeit ändern können. Der Compiler kann nicht davon ausgehen, dass die Maschine, auf der das kompilierte Programm ausgeführt wird, die gleichen Binärdateien hat. Daher kann der C # -Compiler diese Optimierungen nicht legal ausführen, da Sie den Unterschied erkennen können.

Das JIT ist in der Lage, solche Optimierungen vorzunehmen (im Moment nicht).

  

Nun wissen wir, dass die C # -Spezifikation es nicht erlaubt, a zu ändern   Sammlung während der Aufzählung. Also ich glaube es ist technisch sicher   anzunehmen, dass der obige Aufruf von Min () immer den gleichen Wert zurückgibt   für jeden Anruf.

Die Spezifikation von C # kennt nichts über Bibliotheken. Das sagt es gar nicht. Jede Implementierung von IEnumerable kann entscheiden, ob ein solches Verhalten zugelassen werden soll oder nicht.

  

Aber meine Hypothese ist, dass der Compiler wirklich keine Möglichkeit hat, was zu wissen   Das Lambda innerhalb der Erweiterungsmethode Enumerable.Min gibt zurück.

Ja, es könnte alles tun. Zur Laufzeit konnte das JIT solche Eigenschaften ableiten, tut dies aber nicht. Beachten Sie, dass es schwierig ist, selbst grundlegende Fakten abzuleiten, da es Dinge wie Reflektion, Generierung von Laufzeitcode und Multithreading gibt.

  

Stimmt meine Hypothese, oder ist Linq etwas besonderes?   die C # -Spezifikation, die es dem Compiler ermöglicht, in die   Lambda und diesen Aufruf an Min () optimieren?

Nein. LINQ enthält Optimierungen für die Bibliothek. LINQ to Objekte wird genau so ausgeführt, wie Sie es geschrieben haben. Andere LINQ-Anbieter machen das anders.

Wenn Sie sich fragen, ob das JIT einige fortgeschrittene Optimierungen durchführt, ist die Antwort normalerweise keine von .NET 4.5.

    
usr 14.06.2014, 12:09
quelle
2

C # -Compiler funktioniert in Pässen. Jeder Durchlauf benötigt ein komplexes Sprachfeature und konvertiert es in einfacher. Sehr oft geht bei dieser Konvertierung der Kontext verloren. Lambda-Ausdrücke sind einer dieser Schritte. Jedes Lambda wird in eine Klasse konvertiert und diese Klasse wird dann instanziiert und ihre Hauptmethode wird an den Delegaten übergeben. Und der Compilation-Pass schaut nicht einmal in das Lambda. Ein Compiler, der den IL-Code erzeugt, weiß nicht einmal, dass es irgendwelche Lambdas gibt, und sieht nur eine Menge von Klassen. Und diese Klassen geben ihm nicht genug Informationen, um zu folgern, was Sie vorschlagen.

    
Euphoric 14.06.2014 12:24
quelle

Tags und Links