Aquí hay otra técnica que encontré el otro día:
Collections.nCopies(8, 1)
.stream()
.forEach(i -> System.out.println(i));
los Collections.nCopies
llamada crea un List
contenido de n
copias de cualquier valor que proporcione. En este caso, es el Integer
valor en caja 1. Por supuesto, en realidad no crea una lista con n
elementos; crea una lista "virtualizada" que contiene solo el valor y la longitud, y cualquier llamada a get
dentro del rango solo devuelve el valor. El nCopies
método ha existido desde que se introdujo el marco de colecciones en JDK 1.2. Por supuesto, la capacidad de crear una secuencia a partir de su resultado se agregó en Java SE 8.
Gran cosa, otra forma de hacer lo mismo en aproximadamente el mismo número de líneas.
Sin embargo, esta técnica es más rápido que el IntStream.generate
y IntStream.iterate
se acerca, y sorprendentemente, también es más rápido que el IntStream.range
enfoque.
por iterate
y generate
el resultado quizás no sea demasiado sorprendente. El marco de transmisiones (en realidad, los divisores de estas transmisiones) se basa en la suposición de que las lambdas generarán potencialmente diferentes valores cada vez, y que generarán un número ilimitado de resultados. Esto hace que la división en paralelo sea particularmente difícil. El iterate
método también es problemático para este caso porque cada llamada requiere el resultado de la anterior. Entonces, los flujos que usan generate
y iterate
no funcionan muy bien para generar constantes repetidas.
El rendimiento relativamente bajo de range
es sorprendente. Esto también está virtualizado, por lo que no todos los elementos existen en la memoria, y el tamaño se conoce de antemano. Esto debería hacer un spliterator rápido y fácilmente paralelizable. Pero sorprendentemente no le fue muy bien. Quizás la razón es que range
tiene que calcular un valor para cada elemento del rango y luego llamar a una función sobre él. Pero esta función simplemente ignora su entrada y devuelve una constante, por lo que me sorprende que esto no esté alineado y eliminado.
La Collections.nCopies
técnica tiene que hacer boxing / unboxing para poder manejar los valores, ya que no existen especializaciones primitivas de List
. Dado que el valor es el mismo cada vez, básicamente está encuadrado una vez y ese cuadro es compartido por todas las n
copias. Sospecho que el boxeo / unboxing está altamente optimizado, incluso intrinsificado, y puede insertarse bien.
Aquí está el código:
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();
}
Y aquí están los resultados de 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
Hay una buena cantidad de variación en la versión ncopies, pero en general parece cómodamente 20 veces más rápida que la versión de rango. (Sin embargo, estaría dispuesto a creer que hice algo mal).
Me sorprende lo bien que funciona la nCopies
técnica. Internamente, no es muy especial, ya que el flujo de la lista virtualizada simplemente se implementa usando IntStream.range
! Esperaba que fuera necesario crear un spliterator especializado para que esto fuera rápido, pero ya parece bastante bueno.