example - my icon java
¿Por qué la JVM requiere calentamiento? (7)
¿Por qué JVM requiere calentamiento?
Las VM modernas (J) recopilan estadísticas en tiempo de ejecución sobre qué código se usa con más frecuencia y cómo se usa. Un ejemplo (de cientos sino miles) es la optimización de llamadas a funciones virtuales (en jerga de C ++) que solo tienen implementación. Esas estadísticas pueden, por definición, reunirse en tiempo de ejecución.
La carga de clases también es parte del calentamiento, pero obviamente ocurre automáticamente antes de la ejecución del código dentro de esas clases, por lo que no hay mucho de qué preocuparse
¿Qué partes del código debe calentar?
La parte que es crucial para el rendimiento de su aplicación. La parte importante es "calentar" de la misma manera que se usa durante el uso normal, de lo contrario se realizarán las optimizaciones incorrectas (y se desharán más adelante).
Incluso si actualizo algunas partes del código, ¿cuánto tiempo permanece cálido (suponiendo que este término solo signifique cuánto tiempo permanecen en memoria los objetos de su clase)?
Esto es realmente difícil de decir, básicamente, el compilador JIT monitorea constantemente la ejecución y el rendimiento. Si se alcanza un umbral, intentará optimizar las cosas. Seguirá supervisando el rendimiento para verificar que la optimización realmente lo ayude. Si no, podría desoptimizar el código. También pueden ocurrir cosas que invaliden las optimizaciones, como la carga de nuevas clases. Considero que esas cosas no son predecibles, al menos no se basan en una respuesta de stackoverflow, pero hay herramientas que le dicen lo que está haciendo el JIT: https://github.com/AdoptOpenJDK/jitwatch
¿Qué ayuda si tengo objetos que deben crearse cada vez que recibo un evento?
Un ejemplo simple podría ser: usted crea objetos dentro de un método, dado que una referencia deja el alcance del método, esos objetos serán almacenados en el montón, y finalmente recolectados por el recolector de basura. Si el código que utiliza esos objetos es muy utilizado, podría terminar siendo inline en un solo método grande, posiblemente reordenado más allá del reconocimiento, hasta que estos Objetos solo vivan dentro de este método. En ese punto, pueden colocarse en la pila y eliminarse cuando el método finalice. Esto puede ahorrar grandes cantidades de recolección de basura y solo ocurrirá después de un cierto calentamiento.
Con todo lo dicho: soy escéptico ante la idea de que uno necesite hacer algo especial para el calentamiento. Simplemente inicie su aplicación, y úsela y el compilador JIT lo hará bien. Si tiene problemas, aprenda qué hace el JIT con su aplicación y cómo ajustar ese comportamiento o cómo escribir su aplicación para que sea lo más beneficioso.
El único caso donde realmente sé sobre la necesidad de calentamiento son puntos de referencia. Porque si lo descuidas allí obtendrás resultados falsos casi garantizados.
Entiendo que en la máquina virtual Java (JVM), el calentamiento es potencialmente necesario ya que Java carga clases utilizando un proceso de carga diferido y, como tal, desea asegurarse de que los objetos se inicialicen antes de iniciar las transacciones principales. Soy un desarrollador de C ++ y no he tenido que lidiar con requisitos similares.
Sin embargo, las partes que no puedo entender son las siguientes:
- ¿Qué partes del código debería calentar?
- Incluso si caliento algunas partes del código, ¿cuánto tiempo permanece cálido (suponiendo que este término solo signifique cuánto tiempo permanecen en memoria los objetos de su clase)?
- ¿De qué sirve si tengo objetos que deben crearse cada vez que recibo un evento?
Considere, por ejemplo, una aplicación que se espera reciba mensajes en un socket y las transacciones podrían ser Nuevo pedido, Modificar pedido y Cancelar pedido o transacción confirmada.
Tenga en cuenta que la aplicación se trata de High Frequency Trading (HFT), por lo que el rendimiento es de extrema importancia.
¿Qué partes del código debe calentar?
No hay respuesta a esta pregunta en general. Depende completamente de tu aplicación.
Incluso si actualizo algunas partes del código, ¿cuánto tiempo permanece cálido (suponiendo que este término solo signifique cuánto tiempo permanecen en memoria los objetos de su clase)?
Los objetos permanecen en la memoria mientras su programa tenga una referencia a ellos, sin utilizar ningún uso especial de referencia débil o algo similar. Aprender cuándo su programa "tiene una referencia" a algo puede ser un poco más oscuro de lo que podría pensar a primera vista, pero es la base para la gestión de la memoria en Java y vale la pena el esfuerzo.
¿Qué ayuda si tengo objetos que deben crearse cada vez que recibo un evento?
Esto es completamente dependiente de la aplicación. No hay respuesta en general.
Los animo a que estudien y trabajen con Java para comprender cosas como la carga de clases, la administración de la memoria y la supervisión del rendimiento. Toma bastante tiempo crear un objeto, en general lleva más tiempo cargar una clase (lo cual, por supuesto, generalmente se realiza con mucha menos frecuencia). Por lo general , una vez que se carga una clase, permanece en la memoria durante la vida del programa; este es el tipo de cosas que usted debe comprender , no solo obtener una respuesta.
También hay técnicas para aprender si aún no las conoces. Algunos programas usan "grupos" de objetos, se crean instancias antes de que realmente se necesiten y luego se transfieren para procesar una vez que surja la necesidad. Esto permite que una porción de tiempo crítico del programa evite el tiempo empleado en crear instancias durante el período de tiempo crítico. Las agrupaciones mantienen una colección de objetos (10? 100? 1000? 10000?) E instalan más si es necesario, etc. Pero administrar las agrupaciones es un esfuerzo de programación importante, y, por supuesto, ocupa memoria con los objetos en las agrupaciones .
Sería completamente posible usar memoria suficiente para desencadenar la recolección de basura con mucha más frecuencia, y LENTAR EL SISTEMA QUE PRETENDE VELOCIDAD. Es por eso que necesita comprender cómo funciona, no solo "obtener una respuesta".
Otra consideración: por mucho, la mayor parte del esfuerzo puesto en hacer que los programas sean más rápidos se desperdicia, ya que no es necesario. Sin una amplia experiencia con la aplicación que se considera, y / o la medición del sistema, simplemente no se sabe dónde ( o si ) la optimización será incluso notable. El diseño del sistema / programa para evitar casos patológicos de lentitud ES ÚTIL, y no toma casi el tiempo y el esfuerzo de ''optimización''. La mayoría de las veces es todo lo que necesitamos.
- editar - agregar compilación justo a tiempo a la lista de cosas para estudiar y comprender.
¿Qué partes del código debería calentar?
Por lo general, no tienes que hacer nada. Sin embargo, para una aplicación de baja latencia, debe calentar la ruta crítica en su sistema. Deberías tener pruebas unitarias, así que te sugiero que las ejecutes al inicio para calentar el código.
Incluso una vez que su código se haya calentado, debe asegurarse de que las memorias caché de la CPU permanezcan calientes también. Puede ver una disminución significativa en el rendimiento después de una operación de bloqueo, por ejemplo, IO de red, durante hasta 50 microsegundos. Por lo general, esto no es un problema, pero si intenta mantenerse por debajo de 50 micro-segundos la mayor parte del tiempo, esto será un problema la mayor parte del tiempo.
Nota: Warmup puede permitir que el Escape Analysis patee y coloque algunos objetos en la pila. Esto significa que tales objetos no necesitan ser optimizados. Es mejor perfilar la memoria de su aplicación antes de optimizar su código.
Incluso si caliento algunas partes del código, ¿cuánto tiempo permanece cálido (suponiendo que este término solo signifique cuánto tiempo permanecen en memoria los objetos de su clase)?
No hay límite de tiempo. Depende de si el JIt detecta si la suposición que hizo al optimizar el código resultó ser incorrecta.
¿De qué sirve si tengo objetos que deben crearse cada vez que recibo un evento?
Si desea baja latencia o alto rendimiento, debe crear la menor cantidad de objetos posible. Mi objetivo es producir menos de 300 KB / seg. Con esta tasa de asignación, puede tener un espacio Eden lo suficientemente grande como para recolectar menores una vez al día.
Considere, por ejemplo, una aplicación que se espera reciba mensajes en un socket y las transacciones podrían ser Nuevo pedido, Modificar pedido y Cancelar pedido o transacción confirmada.
Sugiero que reutilice objetos tanto como sea posible, aunque si está dentro de su presupuesto de asignación, puede que no valga la pena preocuparse.
Tenga en cuenta que la aplicación se trata de High Frequency Trading (HFT), por lo que el rendimiento es de extrema importancia.
Es posible que le interese nuestro software de código abierto que se utiliza para sistemas HFT en diferentes bancos de inversión y fondos de cobertura.
Mi aplicación de producción se utiliza para el comercio de alta frecuencia y cada bit de latencia puede ser un problema. Está claro que al inicio si no calienta su aplicación, dará lugar a una latencia alta de pocos milis.
En particular, podría estar interesado en https://github.com/OpenHFT/Java-Thread-Affinity ya que esta biblioteca puede ayudar a reducir la inestabilidad de programación en sus hilos críticos.
Y también se dice que las secciones críticas de código que requieren calentamiento deberían ejecutarse (con mensajes falsos) al menos 12K veces para que funcionen de manera optimizada. ¿Por qué y cómo funciona?
El código se compila usando hilo (s) de fondo. Esto significa que, aunque un método puede ser elegible para compilar en código nativo, no significa que lo haya hecho especialmente en el arranque cuando el compilador ya está bastante ocupado. 12K no es irrazonable, pero podría ser mayor.
El calentamiento es raramente requerido . Es relevante al hacer, por ejemplo, pruebas de rendimiento, para asegurarse de que el tiempo de calentamiento JIT no sesgue los resultados.
En el código de producción normal, rara vez aparece el código destinado a la preparación. El JIT se calentará durante el procesamiento normal, por lo que hay muy poca ventaja para introducir código adicional solo para eso. En el peor de los casos, podría estar introduciendo errores, gastando tiempo de desarrollo adicional e incluso perjudicando el rendimiento.
A menos que sepa con certeza que necesita algún tipo de calentamiento, no se preocupe. La aplicación de ejemplo que describes ciertamente no la necesita.
El calentamiento se refiere a tener un fragmento de código que se ejecuta las veces suficientes que la JVM deja de interpretar y compila a nativo (al menos por primera vez). Generalmente eso es algo que no quieres hacer. El motivo es que la JVM recopila estadísticas sobre el código en cuestión que utiliza durante la generación del código (similar a las optimizaciones guiadas por perfil). Entonces, si un fragmento de código en cuestión es "calentado" con datos falsos que tienen propiedades diferentes a los datos reales, podría perjudicar el rendimiento.
EDITAR: Dado que la JVM no puede realizar un análisis estático de todo el programa (no puede saber qué código va a cargar la aplicación), en su lugar puede hacer algunas conjeturas acerca de los tipos de las estadísticas que ha reunido. Como ejemplo al llamar a una función virtual (en C ++) en una ubicación de llamada exacta y determina que todos los tipos tienen la misma implementación, la llamada se promueve a llamada directa (o incluso en línea). Si más tarde esa suposición, si se demuestra que es incorrecta, entonces el código anterior debe estar "sin compilar" para que se comporte correctamente. AFAIK HotSpot clasifica los sitios de llamada como monomórficos (implementación única), biomórficos (exactamente dos transformados en if (imp1-type) {imp1} else {imp2}) y full polmorphic ... dispatch virtual.
Y hay otro caso en el que se produce la recompilación ... cuando tiene una compilación escalonada. El primer nivel perderá menos tiempo tratando de producir un buen código y si el método es "lo suficientemente caliente", se activará el generador de código de tiempo de compilación más caro.
Se trata de un compilador JIT
, que se utiliza en la JVM
para optimizar bytecode en el tiempo de ejecución (porque javac
no puede usar técnicas de optimización avanzadas o agresivas debido a la naturaleza independiente de la plataforma del bytecode)
puedes calentar el código que procesará tus mensajes. De hecho, en la mayoría de los casos no es necesario hacerlo mediante ciclos especiales de calentamiento: simplemente deje que la aplicación inicie y procese algunos de los primeros mensajes:
JVM
intentará hacer todo lo posible para analizar la ejecución del código y optimizarlos :) El calentamiento manual con muestras falsas puede producir resultados aún peoresel código se optimizará después de cierto tiempo y se optimizará hasta que algún evento en el flujo del programa pueda degradar el estado del código (después de que el compilador
JIT
intente optimizar el código nuevamente, este proceso nunca termina)Los objetos de vida corta también deben ser optimizados, pero en general debería ser más eficiente para que su mensaje sea más eficiente.
Siempre lo imaginé como el siguiente:
Usted como (desarrollador de C ++) podría imaginar un enfoque iterativo automatizado mediante la compilación / hotloading / jvm de reemplazar varios bits y piezas con (el análogo imaginario de) las gcc -O0
, -O1
, -O2
, -O2
(y algunas veces revertirlas) si lo considera necesario)
Estoy seguro de que esto no es estrictamente lo que sucede, pero podría ser una analogía útil para un desarrollador de C ++.
En un jvm estándar, el tiempo que tarda un fragmento en ser considerado para jit es establecido por -XX:CompileThreshold
que es 1500 por defecto. (Las versiones de fuentes y jvm varían, pero creo que eso es para jvm8)
Además, un book que tengo a mano indica en Host Performace JIT Chapter (p59) que las siguientes optimizaciones se realizan durante JIT:
- En línea
- Eliminación de bloqueo
- Eliminación de llamadas virtuales
- Eliminación de escritura de memoria no volátil
- Generación de código nativo
EDITAR:
respecto de los comentarios
Creo que 1500 puede ser suficiente para indicar a JAT que debe compilar el código en nativo y dejar de interpretar. ¿estarías de acuerdo?
No sé si es solo una pista, pero dado que openjdk es de código abierto, veamos los diversos límites y números en globals.hpp#l3559@ver-a801bc33b08c (para jdk8u)
(No soy un desarrollador de jvm, este podría ser el lugar completamente equivocado para mirar)
Compilar un código en native no significa necesariamente que también esté optimizado.
A mi entender, es cierto; especialmente si te refieres a -Xcomp
(compilación -Xcomp
), este blog incluso dice que impide que jvm haga ningún perfil, por lo tanto, optimiza si no ejecutas -Xmixed
(valor predeterminado).
Por lo tanto, se activa un temporizador para muestrear el código nativo de acceso frecuente y optimizarlo. ¿Sabes cómo podemos controlar este intervalo de temporizador?
Realmente no conozco los detalles, pero el gobals.hpp
he definido define algunos intervalos de frecuencia.