Voici une autre technique que j'ai utilisée l'autre jour:
Collections.nCopies(8, 1)
.stream()
.forEach(i -> System.out.println(i));
L' Collections.nCopies
appel crée une copie List
contenant la n
valeur que vous fournissez. Dans ce cas, c'est la Integer
valeur encadrée 1. Bien sûr, cela ne crée pas réellement une liste avec des n
éléments; il crée une liste «virtualisée» qui ne contient que la valeur et la longueur, et tout appel à l' get
intérieur de la plage renvoie simplement la valeur. La nCopies
méthode existe depuis que le Framework de collections a été introduit dans le JDK 1.2. Bien sûr, la possibilité de créer un flux à partir de son résultat a été ajoutée dans Java SE 8.
Gros problème, une autre façon de faire la même chose dans à peu près le même nombre de lignes.
Cependant, cette technique est plus rapide que l' approche IntStream.generate
et IntStream.iterate
, et étonnamment, elle est également plus rapide que l' IntStream.range
approche.
Car iterate
et generate
le résultat n'est peut-être pas trop surprenant. Le framework de flux (en fait, les Spliterators pour ces flux) est construit sur l'hypothèse que les lambdas généreront potentiellement des valeurs différentes à chaque fois, et qu'ils généreront un nombre illimité de résultats. Cela rend la division parallèle particulièrement difficile. La iterate
méthode est également problématique dans ce cas car chaque appel nécessite le résultat du précédent. Ainsi, les flux utilisant generate
et iterate
ne fonctionnent pas très bien pour générer des constantes répétées.
La performance relativement médiocre de range
est surprenante. Cela aussi est virtualisé, donc les éléments n'existent pas tous en mémoire et la taille est connue à l'avance. Cela devrait conduire à un séparateur rapide et facilement parallélisable. Mais étonnamment, cela n'a pas très bien fonctionné. Peut-être que la raison est qu'il range
faut calculer une valeur pour chaque élément de la plage, puis appeler une fonction dessus. Mais cette fonction ignore simplement son entrée et renvoie une constante, donc je suis surpris que cela ne soit pas incorporé et tué.
La Collections.nCopies
technique doit faire du boxing / unboxing afin de gérer les valeurs, car il n'y a pas de spécialisations primitives de List
. Puisque la valeur est la même à chaque fois, elle est essentiellement encadrée une fois et cette boîte est partagée par toutes les n
copies. Je soupçonne que la boxe / unboxing est hautement optimisée, voire intrinsèque, et qu'elle peut être bien intégrée.
Voici le 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();
}
Et voici les résultats JMH: (2.8GHz 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
Il y a une bonne quantité de variance dans la version ncopies, mais dans l'ensemble, cela semble confortablement 20 fois plus rapide que la version de gamme. (Je serais tout à fait prêt à croire que j'ai fait quelque chose de mal, cependant.)
Je suis surpris de voir à quel point la nCopies
technique fonctionne. En interne, cela ne fait pas grand-chose de spécial, le flux de la liste virtualisée étant simplement implémenté en utilisant IntStream.range
! Je m'attendais à ce qu'il soit nécessaire de créer un séparateur spécialisé pour que cela aille rapidement, mais cela semble déjà être assez bon.