Hier ist eine andere Technik, die ich neulich kennengelernt habe:
Collections.nCopies(8, 1)
.stream()
.forEach(i -> System.out.println(i));
Der Collections.nCopies
Aufruf erzeugt ein List
enthält n
Kopien von was auch immer Wert , den Sie bieten. In diesem Fall ist es der Boxwert Integer
1. Natürlich wird keine Liste mit n
Elementen erstellt. Es wird eine "virtualisierte" Liste erstellt, die nur den Wert und die Länge enthält, und jeder Aufruf get
innerhalb des Bereichs gibt nur den Wert zurück. Die nCopies
Methode 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.generate
und IntStream.iterate
nähert sich, und überraschend, es ist auch schneller als der IntStream.range
Ansatz.
Denn iterate
und generate
das 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 iterate
Methode ist auch in diesem Fall problematisch, da jeder Aufruf das Ergebnis des vorherigen erfordert. So sind die Ströme mit generate
und iterate
nicht sehr gut tun für wiederholte Konstanten zu erzeugen.
Die relativ schlechte Leistung von range
ist ü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 range
fü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.nCopies
Technik 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 n
Kopien 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 nCopies
Technik 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.