jdk - java version 10.0 2 download
¿Qué hace que las versiones recientes de JVM sean más rápidas? (3)
Recientemente he visto varias afirmaciones que hablan de cómo los lenguajes Java (y los lenguajes basados en JVM, como Scala) son comparables en rendimiento al código C / C ++.
Por ejemplo, de la descripción del proyecto ScalaLab :
La velocidad de las secuencias de comandos basadas en Scala, que se aproxima a la velocidad del código Java nativo y optimizado, y, por lo tanto, está cerca, o incluso mejor, del código científico basado en C / C ++.
¿Alguien puede indicarme un resumen de lo que son estas optimizaciones de JVM? ¿Hay algún punto de referencia real que respalde esta afirmación o que ofrezca una comparación real?
Tecnicas de rendimiento
Primero, depende de qué JVM está hablando, ya que hay varias, pero voy a suponer que se refiere a HotSpot de Oracle (y, en cualquier caso, las otras JVM de primer nivel usarán técnicas similares).
Para esa JVM, esta lista de la wiki interna de HotSpot proporciona un gran comienzo (y las páginas secundarias se detallan en algunas de las técnicas más interesantes). Si solo está buscando una larga lista de trucos, la wiki también lo tiene , aunque para entenderlos probablemente tendrá que buscar en Google los términos individuales.
No todos estos se han implementado recientemente, pero algunos de los grandes lo han hecho (control de rango, análisis de escape, optimizaciones de superposiciones), al menos para una definición vaga de "recientemente".
A continuación, echemos un vistazo a la imagen de rendimiento relativo cuando se trata de C / C ++ vs Java, y por qué las técnicas anteriores ayudan a reducir la brecha o, en algunos casos, a dar a Java una ventaja intrínseca sobre los lenguajes compilados de forma nativa.
Java vs C / C ++
En un nivel alto, las optimizaciones son una mezcla de cosas que verías en cualquier compilador decente para lenguajes nativos como C y C ++, junto con las cosas que se necesitan para reducir los impactos de las características específicas de Java / JVM y las verificaciones de seguridad, como como:
- Análisis de escape que mitiga (de alguna manera) la asignación sin pila para objetos
- Cachés en línea y análisis de jerarquía de clases, que mitigan "todas las funciones son virtuales"
- Eliminación del control de rango, lo que mitiga "todo acceso al array se comprueba"
Muchas de estas optimizaciones * específicas de JVM solo ayudan a que la JVM esté a la par con los idiomas nativos, en el sentido de que están abordando los obstáculos con los que los idiomas nativos no tienen que lidiar. Sin embargo, algunas optimizaciones son cosas que un lenguaje compilado estáticamente no puede administrar (o puede administrar en algunos casos solo con la optimización guiada por perfil, lo cual es raro y, de todos modos, es de talla única):
- Inclusión dinámica de sólo el código más caliente
- Generación de código basada en las ramificaciones / frecuencias reales.
- Generación dinámica de código consciente de conjunto de instrucciones / CPU (incluso funciones de CPU liberadas después de compilar el código) 1
- Elision de codigo nunca ejecutado.
- Inyección de instrucciones de pre-captura intercaladas con código de aplicación
- Toda la familia de técnicas soportadas por safepointing.
El consenso parece ser que Java a menudo produce un código similar en velocidad a los buenos compiladores de C ++ en un nivel de optimización moderado, como gcc -O2, aunque mucho depende del punto de referencia exacto. Las JVM modernas, como HotSpot, tienden a sobresalir en matrices y matrices de bajo nivel (siempre y cuando el compilador de la competencia no sea vectorizante, eso es difícil de superar), o en escenarios con una gran asignación de objetos cuando el código de la competencia realiza una cantidad similar de asignaciones. (La asignación de objetos JVM + GC es generalmente más rápida que malloc), pero se reduce cuando la penalización de la memoria de las aplicaciones Java típicas es un factor, donde la asignación de pila es muy utilizada, o donde los compiladores o intrínsecos vectorizadores inclinan las escalas hacia el código nativo.
Si busca el rendimiento de Java vs C, encontrará muchas personas que han abordado esta pregunta, con diferentes niveles de rigor. Aquí está el primero con el que tropecé , que parece mostrar una relación aproximada entre gcc y HotSpot (incluso en -O3 en este caso). Esta publicación y las discusiones vinculadas son probablemente un mejor comienzo si desea ver cómo un solo punto de referencia puede pasar por varias iteraciones en cada idioma, saltándose unos a otros, y muestra algunos de los límites de optimización en ambos lados.
* Bueno, en realidad no es específico de JVM: la mayoría también se aplicaría a otros idiomas seguros o administrados, como el CLR.
1 Esta optimización en particular es cada vez más relevante a medida que se lanzan nuevos conjuntos de instrucciones (particularmente instrucciones SIMD, pero hay others ) con cierta frecuencia. La vectorización automática puede acelerar algunos códigos de forma masiva , y si bien Java ha estado más lento de lo esperado, al menos se están poniendo al día .
El rendimiento real, por supuesto, depende de los puntos de referencia y difiere según la aplicación. Pero es fácil ver cómo las máquinas virtuales de JIT pueden ser tan rápidas como el código compilado estáticamente, al menos en teoría.
La principal fortaleza del código JIT es que puede optimizar en base a la información conocida solo en tiempo de ejecución. En C, cuando se vincula con una DLL, tendrá que hacer que la función llame cada vez. En un lenguaje dinámico, la función puede estar en línea, incluso si se trata de una función que se cargó en tiempo de ejecución, gracias a la compilación justo a tiempo.
Otro ejemplo es la optimización basada en valores de tiempo de ejecución. En C / C ++ usa una macro de preprocesador para deshabilitar aserciones y tiene que volver a compilar si desea cambiar esta opción. En Java, las aserciones se manejan configurando un campo booleano privado y luego colocando una rama if en el código. Pero como la máquina virtual puede compilar una versión del código que incluyó o no incluyó el código de afirmación según el valor del indicador, hay poco o ningún impacto en el rendimiento.
Otra innovación importante de las máquinas virtuales es la alineación polimórfica. Idomatic Java se centra en gran medida en los métodos de envoltura pequeños como los que obtienen y configuran. Para lograr un buen rendimiento, obviamente es necesario alinearlos. Las funciones polimórficas en línea de la máquina virtual no solo pueden ser utilizadas en el caso común donde solo se llama a un tipo, sino que también puede ser un código en línea que llama a varios tipos diferentes, al incluir un caché en línea con el código apropiado. Si el código comienza a funcionar en muchos tipos diferentes, la VM puede detectar esto y retroceder a un despacho virtual más lento.
Un compilador estático, por supuesto, no puede hacer nada de esto. El análisis estático de gran alcance sólo te lleva hasta ahora. Esto tampoco se limita solo a Java, aunque es el ejemplo más obvio. V8 vm de Google para Javascript también es bastante rápido. Pypy apunta a hacer lo mismo con Python y Rubinius para Ruby, pero no están del todo ahí (ayuda cuando tienes una gran corporación que te respalda).
Me gustaría agregar que hotspot, jrockit y JVM de IBM realizan compresión de pila en el GC. Recientemente porté un código de matemáticas pesado a Scala debido a esta razón. Si tiene la intención de ejecutar cualquier aplicación grande, le recomendaría encarecidamente Java. Es posible que se arrepienta de utilizar CLR cuando se implementa en un servidor o se escala, especialmente si requiere mucha memoria.
También con respecto al código nativo, las opciones de configuración de JVM son excelentes.