tutorialspoint tutorial parameter functions functional expressions expresiones example ejemplos java performance lambda closures java-8

tutorial - Gran diferencia de tiempo de ejecución entre java Lambda vs clase anónima



lambda expressions java (1)

Tenía curiosidad acerca del rendimiento de la creación de instancias lambda java8 contra la misma clase anónima. (Medida realizada en win32 java build 1.8.0-ea-b106). He creado un ejemplo muy simple y he medido si java propone alguna optimización de new operador mientras crea la expresión lambda:

static final int MEASURES = 1000000; static interface ICallback{ void payload(int[] a); } /** * force creation of anonymous class many times */ static void measureAnonymousClass(){ final int arr[] = {0}; for(int i = 0; i < MEASURES; ++i){ ICallback clb = new ICallback() { @Override public void payload(int[] a) { a[0]++; } }; clb.payload(arr); } } /** * force creation of lambda many times */ static void measureLambda(){ final int arr[] = {0}; for(int i = 0; i < MEASURES; ++i){ ICallback clb = (a2) -> { a2[0]++; }; clb.payload(arr); } }

(El código completo puede tomarse allí: http://codepad.org/Iw0mkXhD ) El resultado es bastante predecible: lambda gana 2 veces.

Pero el cambio realmente pequeño para hacer el closure muestra un mal momento para lambda. Clase anónima gana 10 veces! Así que ahora la clase anónima se ve como:

ICallback clb = new ICallback() { @Override public void payload() { arr[0]++; } };

Y lambda hace lo siguiente:

ICallback clb = () -> { arr[0]++; };

(El código completo puede tomarse allí: http://codepad.org/XYd9Umty ) ¿Alguien puede explicarme por qué existe una diferencia tan grande (mala) en el manejo del cierre?


ACTUALIZAR

Algunos comentarios preguntándose si mi punto de referencia en la parte inferior fue defectuoso, después de introducir mucha aleatoriedad (para evitar que el JIT optimice demasiadas cosas), todavía obtengo resultados similares, por lo que tiendo a pensar que está bien.

Mientras tanto, me he encontrado con esta presentación del equipo de implementación de lambda. La página 16 muestra algunas cifras de rendimiento: las clases internas y los cierres tienen un rendimiento similar / la captura de Lambda es hasta 5 veces más rápido.

Y @StuartMarks publicó este enlace muy interesante que analiza el rendimiento lambda. La conclusión es que la compilación posterior a JIT, las lambdas y las clases anónimas funcionan de manera similar en las implementaciones actuales de Hostpot JVM.

Tu banco

También he ejecutado su prueba, como lo ha publicado. El problema es que se ejecuta por tan solo 20 ms para el primer método y 2 ms para el segundo. Aunque esa es una relación de 10: 1, no es representativa de ninguna manera porque el tiempo de medición es demasiado pequeño.

Luego tomé la prueba modificada para permitir más calentamiento de JIT y obtengo resultados similares a los de jmh (es decir, no hay diferencia entre la clase anónima y la lambda).

public class Main { static interface ICallback { void payload(); } static void measureAnonymousClass() { final int arr[] = {0}; ICallback clb = new ICallback() { @Override public void payload() { arr[0]++; } }; clb.payload(); } static void measureLambda() { final int arr[] = {0}; ICallback clb = () -> { arr[0]++; }; clb.payload(); } static void runTimed(String message, Runnable act) { long start = System.nanoTime(); for (int i = 0; i < 10_000_000; i++) { act.run(); } long end = System.nanoTime(); System.out.println(message + ":" + (end - start)); } public static void main(String[] args) { runTimed("as lambdas", Main::measureLambda); runTimed("anonymous class", Main::measureAnonymousClass); runTimed("as lambdas", Main::measureLambda); runTimed("anonymous class", Main::measureAnonymousClass); runTimed("as lambdas", Main::measureLambda); runTimed("anonymous class", Main::measureAnonymousClass); runTimed("as lambdas", Main::measureLambda); runTimed("anonymous class", Main::measureAnonymousClass); } }

La última ejecución toma aproximadamente 28 segundos para ambos métodos.

JMH MICRO BENCHMARK

He ejecutado la misma prueba con jmh y la conclusión es que los cuatro métodos toman tanto tiempo como el equivalente:

void baseline() { arr[0]++; }

En otras palabras, el JIT incluye tanto a la clase anónima como a la lambda y toman exactamente el mismo tiempo.

Resumen de resultados:

Benchmark Mean Mean error Units empty_method 1.104 0.043 nsec/op baseline 2.105 0.038 nsec/op anonymousWithArgs 2.107 0.028 nsec/op anonymousWithoutArgs 2.120 0.044 nsec/op lambdaWithArgs 2.116 0.027 nsec/op lambdaWithoutArgs 2.103 0.017 nsec/op