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.nCopiesappel crée une copie Listcontenant la nvaleur que vous fournissez. Dans ce cas, c'est la Integervaleur 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' getintérieur de la plage renvoie simplement la valeur. La nCopiesmé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.generateet IntStream.iterate, et étonnamment, elle est également plus rapide que l' IntStream.rangeapproche.
Car iterateet generatele 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 iterateméthode est également problématique dans ce cas car chaque appel nécessite le résultat du précédent. Ainsi, les flux utilisant generateet iteratene fonctionnent pas très bien pour générer des constantes répétées.
La performance relativement médiocre de rangeest 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 rangefaut 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.nCopiestechnique 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 ncopies. 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 nCopiestechnique 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.