Soy nuevo en Java 8. Todavía no conozco la API en profundidad, pero he hecho un pequeño punto de referencia informal para comparar el rendimiento de la nueva API de Streams con las colecciones antiguas.
La prueba consiste en filtrar una lista de Integer
, y para cada número par, calcular la raíz cuadrada y almacenarla como resultado List
de Double
.
Aquí está el código:
public static void main(String[] args) {
//Calculating square root of even numbers from 1 to N
int min = 1;
int max = 1000000;
List<Integer> sourceList = new ArrayList<>();
for (int i = min; i < max; i++) {
sourceList.add(i);
}
List<Double> result = new LinkedList<>();
//Collections approach
long t0 = System.nanoTime();
long elapsed = 0;
for (Integer i : sourceList) {
if(i % 2 == 0){
result.add(Math.sqrt(i));
}
}
elapsed = System.nanoTime() - t0;
System.out.printf("Collections: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));
//Stream approach
Stream<Integer> stream = sourceList.stream();
t0 = System.nanoTime();
result = stream.filter(i -> i%2 == 0).map(i -> Math.sqrt(i)).collect(Collectors.toList());
elapsed = System.nanoTime() - t0;
System.out.printf("Streams: Elapsed time:\t\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));
//Parallel stream approach
stream = sourceList.stream().parallel();
t0 = System.nanoTime();
result = stream.filter(i -> i%2 == 0).map(i -> Math.sqrt(i)).collect(Collectors.toList());
elapsed = System.nanoTime() - t0;
System.out.printf("Parallel streams: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));
}.
Y aquí están los resultados para una máquina de doble núcleo:
Collections: Elapsed time: 94338247 ns (0,094338 seconds)
Streams: Elapsed time: 201112924 ns (0,201113 seconds)
Parallel streams: Elapsed time: 357243629 ns (0,357244 seconds)
Para esta prueba en particular, las transmisiones son aproximadamente el doble de lentas que las colecciones, y el paralelismo no ayuda (¿o lo estoy usando de manera incorrecta?).
Preguntas:
- ¿Es justa esta prueba? ¿He cometido algún error?
- ¿Las transmisiones son más lentas que las colecciones? ¿Alguien ha hecho un buen punto de referencia formal sobre esto?
- ¿Por qué enfoque debería luchar?
Resultados actualizados
Ejecuté la prueba 1k veces después del calentamiento JVM (1k iteraciones) según lo aconsejado por @pveentjer:
Collections: Average time: 206884437,000000 ns (0,206884 seconds)
Streams: Average time: 98366725,000000 ns (0,098367 seconds)
Parallel streams: Average time: 167703705,000000 ns (0,167704 seconds)
En este caso, las secuencias son más efectivas. Me pregunto qué se observaría en una aplicación donde la función de filtrado solo se llama una o dos veces durante el tiempo de ejecución.
fuente
IntStream
en su lugar?toList
debe ejecutarse en paralelo, incluso si se recopila en una lista no segura para subprocesos, ya que los diferentes subprocesos se recopilarán en listas intermedias confinadas en subprocesos antes de fusionarse.Respuestas:
Deje de usar
LinkedList
para cualquier cosa que no sea una eliminación pesada del medio de la lista con el iterador.Deje de escribir código de evaluación comparativa a mano, use JMH .
Puntos de referencia adecuados:
Resultado:
Tal como esperaba, la implementación de la transmisión es bastante más lenta. JIT puede incorporar todas las cosas lambda pero no produce un código tan conciso como la versión estándar.
En general, las transmisiones de Java 8 no son mágicas. No podían acelerar las cosas ya bien implementadas (con, probablemente, iteraciones simples o declaraciones for-each de Java 5 reemplazadas por
Iterable.forEach()
yCollection.removeIf()
llamadas). Las transmisiones tienen más que ver con la codificación de conveniencia y seguridad. Conveniencia: la compensación de velocidad está funcionando aquí.fuente
@Benchmark
lugar de@GenerateMicroBenchmark
1) Ve menos de 1 segundo usando su punto de referencia. Eso significa que puede haber una fuerte influencia de los efectos secundarios en sus resultados. Entonces, aumenté tu tarea 10 veces
y ejecutó su punto de referencia. Mis resultados:
sin editar (
int max = 1_000_000
) los resultados fueronEs como sus resultados: la transmisión es más lenta que la recopilación. Conclusión: se dedicó mucho tiempo a la inicialización del flujo / transmisión de valores.
2) Después de aumentar el flujo de tareas se hizo más rápido (eso está bien), pero el flujo paralelo permaneció demasiado lento. Que pasa Nota: tienes
collect(Collectors.toList())
en tu comando. La recolección en una sola colección esencialmente introduce un cuello de botella en el rendimiento y una sobrecarga en caso de ejecución concurrente. Es posible estimar el costo relativo de los gastos generales reemplazandoPara transmisiones se puede hacer por
collect(Collectors.counting())
. Obtuve resultados:¡Eso es para una gran tarea! (
int max = 10000000
) Conclusión: la recolección de artículos para la recolección tomó la mayoría del tiempo La parte más lenta es agregar a la lista. Por cierto, simpleArrayList
se utiliza paraCollectors.toList()
.fuente
collect(Collectors.toList())
en tu comando, es decir, puede haber una situación en la que necesites abordar una Colección individual por varios hilos " . Estoy casi seguro de que setoList
recopila en varias instancias de listas diferentes en paralelo. Solo como el último paso de la colección, los elementos se transfieren a una lista y luego se devuelven. Por lo tanto, no debe haber sobrecarga de sincronización. Esta es la razón por la cual los coleccionistas tienen una función de proveedor, un acumulador y una combinación. (Podría ser lento por otras razones, por supuesto).collect
implementación aquí. Pero al final, varias listas deberían fusionarse en una sola, y parece que la fusión es la operación más pesada en el ejemplo dado.Cambié un poco el código, ejecuté mi Mac Book Pro que tiene 8 núcleos, obtuve un resultado razonable:
Colecciones: Tiempo transcurrido: 1522036826 ns (1.522037 segundos)
Streams: Tiempo transcurrido: 4315833719 ns (4.315834 segundos)
Flujos paralelos: Tiempo transcurrido: 261152901 ns (0.261153 segundos)
fuente
Para lo que estás tratando de hacer, de todos modos no usaría el java api normal. Hay un montón de boxeo / unboxing, por lo que hay una gran sobrecarga de rendimiento.
Personalmente, creo que muchas API diseñadas son basura porque crean una gran cantidad de basura de objetos.
Intente utilizar matrices primitivas de double / int e intente hacerlo de un solo subproceso y vea cuál es el rendimiento.
PD: es posible que desee echar un vistazo a JMH para encargarse de hacer el punto de referencia. Se ocupa de algunos de los escollos típicos, como el calentamiento de la JVM.
fuente