Hier ist eine andere Technik, die ich neulich kennengelernt habe:
Collections.nCopies(8, 1)
.stream()
.forEach(i -> System.out.println(i));
Der Collections.nCopiesAufruf erzeugt ein Listenthält nKopien von was auch immer Wert , den Sie bieten. In diesem Fall ist es der Boxwert Integer1. Natürlich wird keine Liste mit nElementen erstellt. Es wird eine "virtualisierte" Liste erstellt, die nur den Wert und die Länge enthält, und jeder Aufruf getinnerhalb des Bereichs gibt nur den Wert zurück. Die nCopiesMethode gibt es schon seit der Einführung des Collections Framework in JDK 1.2. Natürlich wurde in Java SE 8 die Möglichkeit hinzugefügt, aus seinem Ergebnis einen Stream zu erstellen.
Große Sache, eine andere Möglichkeit, dasselbe in ungefähr der gleichen Anzahl von Zeilen zu tun.
Allerdings ist diese Technik schneller als das IntStream.generateund IntStream.iteratenähert sich, und überraschend, es ist auch schneller als der IntStream.rangeAnsatz.
Denn iterateund generatedas Ergebnis ist vielleicht nicht allzu überraschend. Das Streams-Framework (eigentlich die Spliterators für diese Streams) basiert auf der Annahme, dass die Lambdas möglicherweise jedes Mal unterschiedliche Werte generieren und eine unbegrenzte Anzahl von Ergebnissen generieren. Dies macht eine parallele Aufteilung besonders schwierig. Die iterateMethode ist auch in diesem Fall problematisch, da jeder Aufruf das Ergebnis des vorherigen erfordert. So sind die Ströme mit generateund iteratenicht sehr gut tun für wiederholte Konstanten zu erzeugen.
Die relativ schlechte Leistung von rangeist überraschend. Auch dies ist virtualisiert, sodass nicht alle Elemente im Speicher vorhanden sind und die Größe im Voraus bekannt ist. Dies sollte zu einem schnellen und leicht parallelisierbaren Spliterator führen. Aber es lief überraschenderweise nicht sehr gut. Vielleicht liegt der Grund darin, dass rangefür jedes Element des Bereichs ein Wert berechnet und dann eine Funktion darauf aufgerufen werden muss. Diese Funktion ignoriert jedoch nur ihre Eingabe und gibt eine Konstante zurück. Ich bin überrascht, dass dies nicht inline und getötet ist.
Die Collections.nCopiesTechnik muss Boxen / Unboxen durchführen, um die Werte zu verarbeiten, da es keine primitiven Spezialisierungen von gibt List. Da der Wert jedes Mal der gleiche ist, wird er grundsätzlich einmal eingerahmt und von allen nKopien gemeinsam genutzt. Ich vermute, dass das Boxen / Unboxen stark optimiert ist, sogar intrinsisch, und es kann gut inliniert werden.
Hier ist der Code:
public static final int LIMIT = 500_000_000;
public static final long VALUE = 3L;
public long range() {
return
LongStream.range(0, LIMIT)
.parallel()
.map(i -> VALUE)
.map(i -> i % 73 % 13)
.sum();
}
public long ncopies() {
return
Collections.nCopies(LIMIT, VALUE)
.parallelStream()
.mapToLong(i -> i)
.map(i -> i % 73 % 13)
.sum();
}
Und hier sind die JMH-Ergebnisse: (2,8 GHz Core2Duo)
Benchmark Mode Samples Mean Mean error Units
c.s.q.SO18532488.ncopies thrpt 5 7.547 2.904 ops/s
c.s.q.SO18532488.range thrpt 5 0.317 0.064 ops/s
In der ncopies-Version gibt es einiges an Varianz, aber insgesamt scheint sie bequem 20x schneller zu sein als die Range-Version. (Ich wäre durchaus bereit zu glauben, dass ich etwas falsch gemacht habe.)
Ich bin überrascht, wie gut die nCopiesTechnik funktioniert. Intern macht es nicht viel Besonderes, da der Stream der virtualisierten Liste einfach mit implementiert wird IntStream.range! Ich hatte erwartet, dass es notwendig sein würde, einen speziellen Spliterator zu erstellen, damit dies schnell geht, aber es scheint bereits ziemlich gut zu sein.