Ich bin neu in Java 8. Ich kenne die API noch nicht genau, aber ich habe einen kleinen informellen Benchmark erstellt, um die Leistung der neuen Streams-API mit den guten alten Sammlungen zu vergleichen.
Der Test besteht darin, eine Liste von zu filtern Integer
und für jede gerade Zahl die Quadratwurzel zu berechnen und in einem Ergebnis List
von zu speichern Double
.
Hier ist der Code:
public static void main(String[] args) {
//Calculating square root of even numbers from 1 to N
int min = 1;
int max = 1000000;
List<Integer> sourceList = new ArrayList<>();
for (int i = min; i < max; i++) {
sourceList.add(i);
}
List<Double> result = new LinkedList<>();
//Collections approach
long t0 = System.nanoTime();
long elapsed = 0;
for (Integer i : sourceList) {
if(i % 2 == 0){
result.add(Math.sqrt(i));
}
}
elapsed = System.nanoTime() - t0;
System.out.printf("Collections: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));
//Stream approach
Stream<Integer> stream = sourceList.stream();
t0 = System.nanoTime();
result = stream.filter(i -> i%2 == 0).map(i -> Math.sqrt(i)).collect(Collectors.toList());
elapsed = System.nanoTime() - t0;
System.out.printf("Streams: Elapsed time:\t\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));
//Parallel stream approach
stream = sourceList.stream().parallel();
t0 = System.nanoTime();
result = stream.filter(i -> i%2 == 0).map(i -> Math.sqrt(i)).collect(Collectors.toList());
elapsed = System.nanoTime() - t0;
System.out.printf("Parallel streams: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));
}.
Und hier sind die Ergebnisse für eine Dual-Core-Maschine:
Collections: Elapsed time: 94338247 ns (0,094338 seconds)
Streams: Elapsed time: 201112924 ns (0,201113 seconds)
Parallel streams: Elapsed time: 357243629 ns (0,357244 seconds)
Für diesen speziellen Test sind Streams ungefähr doppelt so langsam wie Sammlungen, und Parallelität hilft nicht (oder verwende ich sie falsch?).
Fragen:
- Ist dieser Test fair? Habe ich einen Fehler gemacht?
- Sind Streams langsamer als Sammlungen? Hat jemand einen guten formalen Maßstab dafür gesetzt?
- Welchen Ansatz sollte ich anstreben?
Aktualisierte Ergebnisse.
Ich habe den Test 1k Mal nach dem Aufwärmen der JVM (1k Iterationen) ausgeführt, wie von @pveentjer empfohlen:
Collections: Average time: 206884437,000000 ns (0,206884 seconds)
Streams: Average time: 98366725,000000 ns (0,098367 seconds)
Parallel streams: Average time: 167703705,000000 ns (0,167704 seconds)
In diesem Fall sind Streams leistungsfähiger. Ich frage mich, was in einer App zu beobachten wäre, in der die Filterfunktion zur Laufzeit nur ein- oder zweimal aufgerufen wird.
quelle
IntStream
stattdessen mit einem versucht ?toList
sollte parallel ausgeführt werden, auch wenn es in einer nicht threadsicheren Liste erfasst wird , da die verschiedenen Threads vor dem Zusammenführen in threadbeschränkten Zwischenlisten erfasst werden.Antworten:
Stop mit
LinkedList
für alles andere als schwer von der Mitte der Liste zu entfernen mit Iterator.Hören Sie auf, Benchmarking-Code von Hand zu schreiben, und verwenden Sie JMH .
Richtige Benchmarks:
Ergebnis:
Genau wie ich erwartet hatte, ist die Stream-Implementierung ziemlich langsamer. JIT ist in der Lage, alle Lambda-Inhalte zu integrieren, produziert jedoch keinen so präzisen Code wie die Vanille-Version.
Im Allgemeinen sind Java 8-Streams keine Zauberei. Sie konnten bereits gut implementierte Dinge nicht beschleunigen (wahrscheinlich mit einfachen Iterationen oder Java 5-Anweisungen für jede Anweisung, die durch
Iterable.forEach()
undCollection.removeIf()
Aufrufe ersetzt wurden). Bei Streams geht es mehr um Codierungskomfort und -sicherheit. Komfort - Geschwindigkeitskompromiss funktioniert hier.quelle
@Benchmark
anstelle von@GenerateMicroBenchmark
1) Mit Ihrem Benchmark sehen Sie eine Zeit von weniger als 1 Sekunde. Das bedeutet, dass Nebenwirkungen einen starken Einfluss auf Ihre Ergebnisse haben können. Also habe ich deine Aufgabe zehnmal erhöht
und lief Ihre Benchmark. Meine Ergebnisse:
ohne edit (
int max = 1_000_000
) Ergebnisse warenEs ist wie bei Ihren Ergebnissen: Der Stream ist langsamer als die Sammlung. Fazit: viel Zeit für die Stream-Initialisierung / die Übertragung von Werten aufgewendet.
2) Nach dem Erhöhen wurde der Task-Stream schneller (das ist in Ordnung), aber der parallele Stream blieb zu langsam. Was ist los mit dir? Hinweis: Sie haben
collect(Collectors.toList())
in Ihrem Befehl. Das Sammeln in einer einzigen Sammlung führt im Wesentlichen zu Leistungsengpässen und Overhead bei gleichzeitiger Ausführung. Es ist möglich, die relativen Gemeinkosten durch Ersetzen zu schätzenFür Streams kann es von gemacht werden
collect(Collectors.counting())
. Ich habe Ergebnisse:Das ist eine große Aufgabe! (
int max = 10000000
) Schlussfolgerung: Das Sammeln von Gegenständen zur Sammlung dauerte die meiste Zeit. Der langsamste Teil ist das Hinzufügen zur Liste. Übrigens wird einfachArrayList
für verwendetCollectors.toList()
.quelle
collect(Collectors.toList())
einen Befehl in Ihrem Befehl, dh es kann vorkommen, dass Sie eine einzelne Sammlung von vielen Threads adressieren müssen. " Ich bin mir fast sicher, dass mehrere Sammelinstanzen paralleltoList
erfasst werden . Erst als letzter Schritt in der Sammlung werden die Elemente in eine Liste übertragen und dann zurückgegeben. Es sollte also keinen Synchronisationsaufwand geben. Aus diesem Grund haben Sammler sowohl eine Lieferanten- als auch eine Akkumulator- und eine Kombiniererfunktion. (Es könnte natürlich aus anderen Gründen langsam sein.)collect
Implementierung. Am Ende sollten jedoch mehrere Listen zu einer einzigen zusammengeführt werden, und es sieht so aus, als ob das Zusammenführen in einem bestimmten Beispiel die schwerste Operation ist.Ich ändere den Code ein wenig, lief auf meinem MacBook Pro, das 8 Kerne hat, ich habe ein vernünftiges Ergebnis erhalten:
Sammlungen: Verstrichene Zeit: 1522036826 ns (1,522037 Sekunden)
Streams: Verstrichene Zeit: 4315833719 ns (4,315834 Sekunden)
Parallele Streams: Verstrichene Zeit: 261152901 ns (0,261153 Sekunden)
quelle
Für das, was Sie versuchen, würde ich sowieso keine normalen Java-APIs verwenden. Es gibt eine Menge Boxen / Unboxen, also gibt es einen enormen Leistungsaufwand.
Persönlich denke ich, dass viele APIs Mist sind, weil sie viel Objektmüll erzeugen.
Versuchen Sie, ein primitives Array von double / int zu verwenden, und versuchen Sie es mit einem einzigen Thread, um die Leistung zu ermitteln.
PS: Vielleicht möchten Sie sich JMH ansehen, um den Benchmark durchzuführen. Es behebt einige der typischen Fallstricke wie das Aufwärmen der JVM.
quelle