2016-06-29 4 views
10

Seit der Einführung von Java 8 habe ich mich wirklich an Lambdas gewöhnt und benutze sie wann immer es möglich war, meistens um mich daran zu gewöhnen. Eine der gebräuchlichsten Anwendungen ist es, wenn ich eine Sammlung von Objekten iterieren und darauf reagieren möchte. In diesem Fall wähle ich entweder forEach oder stream(). Ich schreibe selten die alte for(T t : Ts) Schleife und fast hätte ich die for(int i = 0.....) vergessen.Wie wird zwischen Lambda-Iteration und normaler Schleife entschieden?

Allerdings diskutierten wir dies neulich mit meinem Vorgesetzten und er sagte mir, dass Lambdas nicht immer die beste Wahl sind und manchmal die Leistung behindern können. Aus einer Vorlesung, die ich zu diesem neuen Feature gesehen hatte, hatte ich das Gefühl, dass Lambda-Iterationen immer vollständig vom Compiler optimiert werden und (immer?) Besser sein werden als bloße Iterationen, aber er möchte anders sein. Ist das wahr? Wenn ja, wie unterscheide ich die beste Lösung in jedem Szenario?

S.S: Ich bin nicht über Fälle sprechen, in denen es empfohlen wird, parallelStream anzuwenden. Offensichtlich werden diese schneller sein.

+5

Parallele Streams können langsamer als normale Loops sein, wenn die CPU-Anforderungen und/oder die Stream-Größe nicht groß ist. Threads sind teuer zu verwalten. – Bohemian

+1

Der Overhead des Stream-Frameworks kann überraschend sein. Messen! –

+0

In der Tat und so habe ich meine Frage aktualisiert. – Konstantine

Antwort

1

Es hängt von der spezifischen Implementierung ab.

Im Allgemeinen forEach Methode und foreach Schleife über Iterator haben in der Regel ziemlich ähnliche Leistung, da sie ähnliche Abstraktionsebene verwenden. stream() ist normalerweise langsamer (oft um 50-70%), da es eine weitere Ebene hinzufügt, die Zugriff auf die zugrunde liegende Sammlung bietet.

Die Vorteile von stream() sind in der Regel die mögliche Parallelität und einfache Verkettung der Operationen mit vielen wiederverwendbaren von JDK zur Verfügung gestellt.

+1

Auf den Punkt, so 'List.forEach' zahlt keine Leistung Strafe, aber leider nicht die Ausdruckskraft von' List.stream /(). ForEach'. –

+1

Ich stimme nicht mit der Aussage von Streams überein, die 50-70% langsamer sind. In den meisten Fällen beruhen diese Zahlen auf falschen Benchmarks, die den erstmaligen Overhead messen. – Holger

2

Die Leistung hängt von so vielen Faktoren ab, dass es schwer vorherzusagen ist. Normalerweise würden wir sagen, wenn Ihr Vorgesetzter behauptet, dass es ein Problem mit der Leistung gab, ist Ihr Vorgesetzter dafür verantwortlich, zu erklären, welches Problem.

Eine Sache, vor der jemand Angst haben könnte, ist, dass hinter den Kulissen eine Klasse für jede Lambda-Erstellungsstelle (mit der aktuellen Implementierung) generiert wird. Wenn der betreffende Code nur einmal ausgeführt wird, kann dies als a betrachtet werden Verschwendung von Ressourcen. Dies stimmt mit der Tatsache überein, dass Lambda-Ausdrücke einen höheren Initialisierungsaufwand als der normale Imperativ-Code haben (wir vergleichen hier nicht mit inneren Klassen), so dass in Klassen-Initialisierern, die nur einmal ausgeführt werden, in Betracht gezogen wird, sie zu vermeiden. Dies entspricht auch der Tatsache, dass Sie never use parallel streams in class initializers sollten, damit dieser potenzielle Vorteil hier sowieso nicht zur Verfügung steht.

Für normalen, häufig ausgeführten Code, der wahrscheinlich von der JVM optimiert wird, treten diese Probleme nicht auf. Wie Sie richtig angenommen haben, erhalten Klassen, die für Lambda-Ausdrücke generiert wurden, die gleiche Behandlung (Optimierungen) wie andere Klassen. An diesen Orten kann forEach auf Sammlungen effizienter als eine for Schleife.

Die temporären Objektinstanzen für ein Iterator oder den Lambda-Ausdruck erstellt sind vernachlässigbar, es könnte jedoch erwähnenswert, dass eine foreach-Schleife wird immer eine Iterator Instanz während lambda expression do not always do erstellen. Während die default Implementierung von Iterable.forEach auch eine Iterator erstellen wird, nutzen einige der am häufigsten verwendeten Sammlungen die Möglichkeit, eine spezialisierte Implementierung bereitzustellen, vor allem ArrayList.

Die ArrayList 's forEach ist im Grunde eine for Schleife über ein Array, ohne Iterator. Es wird dann die accept-Methode der Consumer aufrufen, die eine generierte Klasse sein wird, die eine triviale Delegation an die synthetische Methode enthält, die den Code Ihres Lambda-Ausdrucks enthält. Um die gesamte Schleife zu optimieren, muss der Horizont des Optimierers die Schleife ArrayList über ein Array (ein allgemeines Idiom, das für einen Optimierer erkennbar ist) überspannen, die synthetische accept Methode, die eine triviale Delegation und die Methode enthält, die Ihren tatsächlichen Code enthält.

Im Gegensatz dazu, wenn sie über die gleiche Liste Iterieren einer foreach-Schleife, eine Iterator Implementierung wird geschaffen, um die ArrayList Iterationslogik enthält, verteilt auf zwei Methoden, hasNext() und next() und Instanzvariablen des Iterator. Die Schleife wird das Verfahren wiederholt aufrufen hasNext() die Endbedingung (index<size) und die next() die Bedingung Bereiche nochmals zu überprüfen, bevor das Element zurückkehrt, da es keine Garantie ist, dass der Anrufer richtig hasNext() vor next() nicht aufrufen. Natürlich ist ein Optimierer in der Lage, diese Duplizierung zu entfernen, aber das erfordert mehr Aufwand als gar nicht. Um die gleiche Leistung wie die forEach-Methode zu erhalten, muss der Horizont des Optimierers Ihren Schleifencode, die nicht-triviale hasNext() Implementierung und die nicht-triviale next() Implementierung umfassen.

Ähnliche Dinge können auch für andere Sammlungen mit einer spezialisierten forEach Implementierung gelten. Dies gilt auch für Stream-Operationen, wenn die Quelle eine spezialisierte Spliterator-Implementierung bereitstellt, die die Iterationslogik nicht über zwei Methoden wie Iterator verteilt.

Also, wenn Sie die technischen Aspekte von for jeweils vs forEach(…) diskutieren möchten, können Sie diese Informationen verwenden.

Aber wie gesagt, beschreiben diese Aspekte nur mögliche Leistungsaspekte, da die Arbeit des Optimierers und andere Laufzeitumgebungsaspekte das Ergebnis vollständig verändern können. Ich denke, als Faustregel gilt, je kleiner der Schleifenkörper/die Aktion ist, desto geeigneter ist die forEach Methode. Dies harmoniert perfekt mit der Vorgabe, zu lange Lambda-Ausdrücke zu vermeiden.

Verwandte Themen