Ich bin gespannt, ob for..in
aus Leistungsgründen der .each
vorgezogen werden sollte.
For .. in
ist Teil der Standardsprachflusskontrolle.
Stattdessen ruft each
eine Schließung mit einem zusätzlichen Overhead auf.
.each {...}
ist Syntaxzucker, der dem Methodenaufruf .each({...})
Außerdem können Sie wegen der Tatsache, dass es sich um eine Schließung handelt, innerhalb von each
code block die Anweisungen break
und continue
nicht verwenden, um die Schleife zu steuern.
Aktualisierter Benchmark Java 1.8.0_45 Groovy 2.4.3:
Hier ist ein weiterer Benchmark mit 100000 Iterationen:
%Vor%Ergebnisse:
Lassen Sie uns einen theoretischen Blick darauf werfen, was für Anrufe dynamisch erledigt werden und welche Anrufe direkter mit Java-Logik erledigt werden (ich werde diese statischen Anrufe anrufen).
Im Falle von for-in
arbeitet Groovy auf einem Iterator, um es zu bekommen, wir haben einen dynamischen Aufruf von iterator (). Wenn ich mich nicht irre, werden die Aufrufe hasNext und next mit der normalen Aufruflogik der Java-Methode ausgeführt. Somit haben wir für jede Iteration nur 2 statische Aufrufe. Je nach Benchmark ist zu beachten, dass dieser erste Aufruf von iterator () zu ernsthaften Initialisierungszeiten führen kann, da dies das Meta-Klassensystem initalisieren kann und das einen Moment dauert.
Im Fall von each
haben wir sowohl den dynamischen Aufruf an sich selbst als auch die Objekterstellung für den geöffneten Block (Instanz von Closure). jeder (Closure) wird dann auch iterator () aufrufen, aber nicht zwischengespeichert ... also alle einmaligen Kosten. Während der Schleife erfolgt die Ausführung von hasNext und next mithilfe von Java-Logik, die zwei statische Aufrufe durchführt. Der Aufruf der Closure-Instanz erfolgt mit Java-Standardlogik für den Methodenaufruf, der dann doCall mithilfe eines dynamischen Aufrufs aufruft.
Um es zusammenzufassen, pro% iteration verwendet for-in
nur 2 statische Aufrufe, während each
3 statische und 1 dynamische Aufrufe hat. Der dynamische Aufruf ist viel langsamer als mehrere statische Aufrufe und viel schwieriger für die JVM zu optimieren, wodurch das Timing dominiert wird. Als Folge davon sollte each
immer langsamer sein, solange der offene Block den dynamischen Aufruf benötigt.
Wegen der komplizierten Logik für Closure # Call ist es schwierig, den dynamischen Call Away zu optimieren. Und das ist ärgerlich, weil es nicht wirklich benötigt wird und entfernt wird, sobald wir einen Workaround finden. Sollte uns das jemals gelingen, wäre each
vielleicht noch langsamer, aber es ist eine viel schwierigere Sache, da Bytecode-Größen und Aufrufprofile hier eine Rolle spielen. In der Theorie könnten sie dann gleich sein (Ignorieren der Initialisierungszeit), aber die JVM hat viel mehr zu tun. Dasselbe gilt natürlich auch für die strombasierte Lambda-Verarbeitung in Java8,
Tags und Links groovy microbenchmark