Informe de Java Virtual Machine Language Summit 2019


Hoy finalizó la duodécima Cumbre JVM LS . Como de costumbre, fue un evento hardcore con presentaciones técnicas en máquinas virtuales y los idiomas que se ejecutan en ellas. Como de costumbre, la cumbre se celebró en Santa Clara, en el campus de Oracle. Como es habitual, hay muchas más personas que desean llegar aquí que lugares: el número de participantes no supera los 120. Como es habitual, no hubo marketing, solo despojos.


Esta cumbre ya es la tercera para mí, y cada vez que la visito con gran placer, a pesar del terrible desfase horario. Aquí no solo puede escuchar informes, sino también conocer a mejores personas del mundo de JVM, participar en conversaciones informales, hacer preguntas en talleres y, en general, sentirse involucrado en grandes logros.


Si no asistió a la cumbre, no importa. La mayoría de los informes se publican en YouTube casi inmediatamente después de la cumbre. En realidad ya están disponibles . Para facilitar la navegación, describiré brevemente aquí todos los informes y talleres a los que logré asistir.


29 de julio


Ghadi Shayban - Futuros Clojure


No se trata de las características de la compilación futura en el lenguaje Clojure , como muchos pensaron, sino simplemente del desarrollo del lenguaje, las complejidades de la generación de código y los problemas que encuentran. Por ejemplo, resultó que en Clojure es importante anular las variables locales después del último uso, porque si el encabezado de una lista que se genera de manera perezosa en una variable local, luego de anularla, el recolector de basura no puede recopilar los nodos que ya han sido anulados, y el programa puede bloquearse con OutOfMemory . En general, el compilador JIT C2 mismo libera las variables después del último uso, pero el estándar no garantiza esto y, por ejemplo, el intérprete HotSpot no.


También fue interesante aprender sobre la implementación del despacho dinámico de llamadas a funciones. También aprendí que hasta hace poco, Clojure estaba apuntando a JVM 6 y solo recientemente cambié a JVM 8. Ahora los autores del compilador miran invocacionalmente dinámico.


Alan Bateman y Rickard Bäckman - Actualización de Project Loom


El proyecto Loom es una fibra ligera para Java. Hace un año, Alan y Ron ya hablaron sobre este proyecto, y luego parecía que todo iba muy bien y estaba a punto de estar listo. Sin embargo, este proyecto aún no ha entrado oficialmente en Java y todavía se está desarrollando en una bifurcación separada del repositorio. Por supuesto, resultó que era necesario resolver muchos detalles.


Muchas API estándar, desde ReentrantLock.lock hasta Socket.accept, ya están adaptadas para fibras: si dicha llamada se realiza dentro de una fibra, se guardará el estado de ejecución, la pila se desenrollará y el subproceso del sistema operativo se liberará para otras tareas hasta que un evento despierte la fibra (por ejemplo, ReentrantLock.unlock). Sin embargo, por ejemplo, el viejo bloque sincronizado todavía no funciona y, al parecer, no puede funcionar sin una refactorización seria de todo el soporte de sincronización en la JVM. Otro desbobinado de la pila no funcionará si hay marcos nativos en la pila entre el inicio de la fibra y el punto de interrupción. En ambos casos, nada explota, pero la fibra no libera el flujo.


Hay muchas preguntas sobre cómo se compara Fiber con la antigua clase java.lang.Thread. Hace un año, surgió la idea de hacer de Fiber una subclase de Thread. Ahora lo han rechazado y lo convierten en una entidad independiente, porque emular en cada fibra todo el comportamiento de una transmisión regular es bastante costoso. En este caso, Thread.currentThread () dentro de la fibra devolverá el blende generado, y no el hilo real en el que se ejecuta todo. Pero el inconveniente se comportará bastante bien (aunque puede ralentizar el trabajo). La idea importante es, bajo ninguna circunstancia, transmitir el flujo de medios real en el que se ejecuta la fibra dentro de la fibra. Esto puede ser peligroso ya que una fibra puede moverse fácilmente a otro hilo. El engaño continuará.


Es curioso que los participantes del proyecto ya hayan introducido algunos cambios preparatorios en el repositorio principal de JDK para facilitarles la vida. Por ejemplo, en Java 13, el método doPrivileged se reescribió desde el código nativo completamente en Java, obteniendo un aumento de rendimiento de aproximadamente 50 veces. ¿Por qué es un proyecto de Loom? El hecho es que este método en particular aparece muy a menudo en el medio de la pila, y aunque era nativo, las fibras con esta pila no se detuvieron. De una forma u otra, el proyecto ya se está beneficiando.


En la página del proyecto, puede leer la documentación y descargar el árbol fuente, y también hay ensamblados binarios que puede tomar y reproducir hoy. Esperamos que en los próximos años todo se integre.


Brian Goetz - Taller "Proyecto Amber"


Paralelamente, se estaba realizando un taller sobre el proyecto Loom, pero fui a Amber. Aquí discutimos brevemente los objetivos del proyecto y los principales JEP en los que se está trabajando: coincidencia de patrones , registros y tipos sellados . Luego, toda la discusión se centró en el tema privado del alcance. Hablé sobre esto en la conferencia de Joker el año pasado, en principio, no se dijo nada muy nuevo. Traté de impulsar una idea con tipos de unión implícitos como if(obj instanceof Integer x || obj instanceof Long x) use(x.longValue()) , pero no vi entusiasmo.


Jean Christophe Beyler, Arthur Eubanks y Man Cao - Thread Sanitizing para Java


En todos los aspectos, un maravilloso proyecto de Google para buscar carreras utilizando datos en forma de lectura y escritura del mismo campo no volátil o elemento de matriz de diferentes flujos sin establecer una relación de antes. El proyecto se escribió originalmente como un módulo LLVM para código nativo, y ahora se ha adaptado para HotSpot. Este es un proyecto oficial de OpenJDK con su lista de correo y repositorio.


Según los autores, la cosa ahora está funcionando bastante bien, puedes ensamblar y jugar. Además, encuentra las carreras no solo en el código Java, sino también en el código de las bibliotecas nativas. Las carreras en el código de la máquina virtual en sí no se buscan, porque todas las primitivas de sincronización se escriben a su manera, y TSan no puede detectarlas. Según los autores, TSan no da falsos positivos.


El principal problema es el rendimiento. Ahora solo el intérprete está instrumentado para el código Java, respectivamente, la compilación JIT está completamente deshabilitada y el intérprete, que ya es lento, se ralentiza varias veces. Pero si tiene suficientes recursos (Google tiene, por supuesto, suficientes), ocasionalmente puede manejar sus suites de prueba usando TSan. También se planea agregar instrumentación al JIT, pero esta es una intervención mucho más seria en el JVM.


Alguien preguntó si deshabilitar la compilación JIT no afecta el resultado, porque algunas carreras pueden no aparecer en el intérprete. El orador no descartó esta posibilidad, pero dijo que ya habían encontrado una gran cantidad de carreras que les tomaría mucho tiempo recoger. Por lo tanto, tenga cuidado al ejecutar su proyecto bajo TSan: puede descubrir la verdad desagradable.


Brian Goetz - Actualización Valhalla


Todos están esperando los tipos de valores en Java, pero nadie sabe cuándo aparecerán. Sin embargo, los movimientos son cada vez más serios. Ya hay conjuntos binarios de prueba con el hito L2 actual. En los planes actuales, el Valhalla completo llegará al hito L100, pero los autores siguen siendo optimistas y creen que se ha hecho más del dos por ciento.


Entonces, desde el punto de vista del lenguaje, tenemos clases con el modificador en línea, que son procesadas de manera especial por la máquina virtual. Las instancias de tales clases se pueden incrustar en otros objetos, y también son posibles matrices planas que contienen instancias de clases en línea. La instancia no tiene un encabezado, lo que significa que no hay identidad, el código hash se calcula por campos, == también por campos, un intento de sincronización o Object.wait() en dicha clase generará una IllegalMonitorStateException. Escribir null en una variable de este tipo, por supuesto, no funcionará. Sin embargo, los autores ofrecen una alternativa: si ha declarado un Point clase en línea, entonces puede declarar un campo o una variable de tipo (¡sorpresa-sorpresa!) Point? , y luego habrá un objeto de pleno derecho en el montón (como el boxeo) con un encabezado, identidad y null encajará allí.


Las preguntas abiertas serias siguen siendo la especialización de genéricos y la migración de clases existentes (por ejemplo, Optional ) a una clase en línea para no romper el código existente (sí, las personas escriben null en variables de tipo Optional ). Sin embargo, la imagen se cierne y la brecha es visible.


David Wrighton y Neal Gafter - Tipos de valor en el CLR


Me sorprendió que el mismo Neil Gufter, coautor de los rompecabezas originales de Java, ahora trabaja en Microsoft en tiempo de ejecución .Net. También fue una sorpresa ver un informe sobre el CLR (el llamado tiempo de ejecución .Net) en el JVM LS. Pero familiarizarse con la experiencia de colegas de otros mundos siempre es útil. El informe habla sobre las variedades de referencias y punteros en el CLR, sobre las instrucciones de código de bytes utilizadas para los tipos de valor y sobre cuán bellamente especializadas están las funciones generalizadas como reducir. Fue interesante saber que uno de los objetivos de los tipos de valor en .Net es una interoperabilidad con código nativo. Debido a esto, la ubicación de los campos en los tipos de valor está estrictamente fija y puede proyectarse en una estructura sin transformaciones. La JVM nunca ha tenido una tarea de este tipo y qué hacer con la interoperabilidad nativa: consulte a continuación.


Vladimir Ivanov y John Rose - Vectores y los números en la JVM


Nuevamente actualice el informe del año pasado . Una vez más, la pregunta es por qué todavía no han lanzado nada, si hace un año todo se veía bastante bien.


Un vector es una colección de varios números, que en hardware se pueden representar mediante un único registro de vectores como zmm0 para AVX512. En los vectores, puede cargar datos de matrices, realizar operaciones en ellos como la multiplicación por elementos y devolverlos. Todas las operaciones para las que hay instrucciones del procesador están intrínsecas en el compilador JIT en estas instrucciones. El número de operaciones es simplemente enorme. Si falta algo, se usa una implementación lenta alternativa. Idealmente, los objetos intermedios vectoriales no se crean; el análisis de escape funciona. Todos los algoritmos informáticos estándar están vectorizados con una explosión, utilizando toda la potencia de su procesador.


Desafortunadamente, es difícil para los autores no tener valgalla: el análisis de escape es frágil y podría no funcionar fácilmente. Estos vectores simplemente deben ser clases en línea, entonces todos los problemas desaparecerán. No está claro si esta API puede incluso lanzarse antes de la primera versión de Valgalla. Parece mucho más listo. Entre los problemas llamados dificultades con el soporte del código. Hay muchas piezas repetidas para diferentes tamaños de registros y diferentes tipos de datos, por lo que la mayor parte del código se genera a partir de plantillas y duele mantenerlo.


El uso también es imperfecto. No hay sobrecarga del operador en Java, por lo que la matemática se ve fea: en lugar de max(va-vb*42, 0) debe escribir va.lanewise(SUB, vb.lanewise(MUL, 42)).lanewise(MAX, 0) . Sería bueno tener acceso a AST lambdas como en C #. Entonces sería posible generar una operación lambda personalizada como MYOP = binOp((va, vb) -> max(va-vb*42, 0)) y usarla.


30 de julio


El segundo día pasó bajo la bandera de la compilación.


Mark Stoodley - ¡De AOT a JIT y más allá!


Un empleado de IBM, miembro del proyecto JVM OpenJ9, habla sobre su experiencia con la compilación JIT y AOT. Siempre hay problemas: JIT es un inicio lento, porque se está calentando; Costos de CPU para la compilación. AOT: rendimiento subóptimo debido a la falta de un perfil (es posible crear un perfil, pero no trivialmente y no siempre el perfil durante la compilación coincide con el perfil en la ejecución), es más difícil de usar, se vincula a la plataforma de destino, sistema operativo, recolector de basura. Algunos de los problemas se pueden resolver combinando enfoques: comenzando con el código compilado por AOT y luego terminando con JIT. Una buena alternativa a todo esto es el almacenamiento en caché de JIT. Si tiene muchas máquinas virtuales (hola, microservicios), todas recurren a un servicio separado: el compilador JIT (sí, JITaaS), donde todo es como un adulto, orquestación, equilibrio de carga. Este servicio compila. Muy a menudo, puede dar código listo para un determinado método, porque este método ya se ha compilado en otra JVM. Esto mejora enormemente el calentamiento, elimina el consumo de recursos de su servicio JVM y, en general, reduce el consumo total de recursos.


En general, JITaaS podría ser la próxima palabra de moda en el mundo JVM. Desafortunadamente, no entendí si esto podría jugarse ahora o si todavía es un desarrollo cerrado.


Christian Wimmer - Mejorando la imagen nativa de GraalVM


GraalVM Native Image es una aplicación Java compilada en código nativo que se ejecuta sin JVM (a diferencia de los módulos compilados utilizando un compilador AOT como jaotc). Más precisamente, esta no es una aplicación Java. Para funcionar correctamente, necesita un mundo cerrado, es decir, todo el código debe estar visible en la etapa de compilación, no Class.forName. Puede manejar los reflejos y los métodos, pero cuando compila debe indicar específicamente qué clases y métodos se utilizarán a través de la reflexión.


Otra cosa divertida es la inicialización de clase. Muchas clases se inicializan durante la compilación. Es decir, sus compiladores calcularán sus campos estáticos de manera predeterminada y el resultado se escribirá en la imagen ensamblada, y cuando inicie la aplicación, simplemente se leerá. Esto es necesario para lograr una mejor calidad de compilación: cualquier plegado constante se puede hacer si el compilador conoce los valores de los campos estáticos. Todo está bien con JIT, el intérprete realiza una inicialización estática y luego, conociendo las constantes, puede compilar. Y al construir una aplicación nativa, tienes que engañar. Esto, por supuesto, conduce a divertidos efectos psicodélicos. Por lo tanto, las clases generalmente se inicializan en el orden en que se accede, y durante la compilación este orden es desconocido y es posible la inicialización en otro. Si hay referencias circulares entre los inicializadores de clase, puede ver la diferencia en el comportamiento del código JVM y en la imagen nativa.


Taller Schatzl - Hotspot GC.


Solucionó todo el dolor asociado con los recolectores de basura. Lamentablemente, escuché a la mayoría. Recuerdo que se discutió el recuerdo de la memoria del sistema operativo, incluido el asqueroso Xmx para todos. Hay buenas noticias: en Java 13 se agrega una nueva opción -XX: SoftMaxHeapSize. Hasta ahora, solo es compatible con el recopilador ZGC, pero G1 también puede ponerse al día. Establece un límite en el tamaño del montón, que no debe superarse, excepto en situaciones de emergencia, cuando no funciona de manera diferente. Por lo tanto, puede establecer un Xmx grande (digamos, igual al tamaño de toda la RAM) y algunos SoftMaxHeapSize razonables. Entonces, la JVM se mantendrá dentro del marco la mayor parte del tiempo, pero en la carga máxima todavía no arrojará OutOfMemoryError, pero tomará más memoria del sistema operativo. Cuando la carga cae, la memoria volverá.


Mei-Chin Tsai - JIT y AOT en el CLR


Microsoft Mei-Chin Tsai habló sobre las características de la compilación JIT y AOT en el CLR. La compilación AOT se ha desarrollado para ellos durante mucho tiempo, pero inicialmente (ngen.exe) se llevó a cabo en la plataforma de destino, como la primera vez que se inició (si tiene Windows, busque los archivos * .ni.dll en la carpeta de Windows). Los archivos se obtienen dependiendo de la versión de Windows local e incluso de otras DLL-ek. En consecuencia, si la dependencia se actualiza, todos los módulos nativos deben volver a compilarse. En la segunda generación (crossgen), los autores precompilaron aplicaciones y módulos relativamente independientes de las versiones y dependencias de hardware y sistema operativo. Esto ralentizó el código porque las llamadas de dependencia ahora tenían que hacerse honestamente virtuales. Este problema se resolvió conectando JIT y volviendo a compilar el código activo durante la aplicación. Luego hablamos sobre la compilación multinivel (en niveles) (parece que en el CLR esto está en su infancia, mientras que se ha desarrollado en Java durante al menos diez años) y sobre los planes futuros para hacer que AOT sea verdaderamente multiplataforma.


Wei Kuai y Xiaoming Gu: acelerar el rendimiento de JVM con JWarmUp


Los colegas de Alibaba presentaron su enfoque al problema de calentamiento de JVM. Usan la JVM para muchos servicios web. En principio, un inicio muy rápido no es tan importante, porque el equilibrador siempre puede esperar hasta que la máquina se inicie y solo entonces comenzar a enviarle solicitudes. Sin embargo, el problema es que la máquina no se calienta sin solicitudes: no se llama al código que describe la lógica para procesar las solicitudes, lo que significa que no se compila. Se compilará cuando lleguen las primeras solicitudes, es decir, no importa cuánto espere el equilibrador, habrá un error de rendimiento en las primeras solicitudes. Anteriormente, intentaron resolver esto lanzando solicitudes falsas al próximo servicio antes de enviarle solicitudes reales. El enfoque es interesante, pero es bastante difícil generar una secuencia tan falsa que provocaría la compilación de todo el código necesario.


Un problema separado es la desoptimización. En las primeras mil consultas, una if siempre fue a lo largo de la primera rama, el compilador JIT generalmente lanzó la segunda, insertando una trampa de desoptimización allí para reducir el tamaño del código. Pero la solicitud número 1001 fue a la segunda rama, la desoptimización funcionó y todo el método fue al intérprete. Mientras se compilan las estadísticas nuevamente, mientras el compilador C1 compila el método, luego el perfil completo del compilador C2, los usuarios experimentarán una desaceleración. Y luego, en el mismo método, otro puede ser desoptimizado, y todo irá en uno nuevo.


JWarmUp resuelve el problema de la siguiente manera. Durante la primera ejecución del servicio, se escribe un registro de compilación durante varios minutos: registra qué métodos se compilaron y la información de perfil necesaria por ramas, tipos, etc. Si este servicio se reinicia, inmediatamente después del inicio, se inicializan todas las clases del registro y se compilan los métodos registrados. teniendo en cuenta el perfil anterior. Como resultado, el compilador funcionará bien al inicio, después de lo cual el equilibrador comenzará a enviar solicitudes a esta JVM. En este momento, todo el código caliente que ya ha sido compilado.


Vale la pena señalar que el problema de inicio rápido no se resuelve aquí. Un lanzamiento puede ser aún más lento porque se compilan muchos métodos, algunos de los cuales pueden ser necesarios solo unos minutos después del lanzamiento. Pero el registro resulta ser reutilizable: a diferencia de AOT, puede elevar el servicio en una arquitectura diferente o con un recolector de basura diferente y reutilizar el registro anterior.


Los autores han intentado durante mucho tiempo empujar JWarmUp a OpenJDK. Hasta ahora sin éxito, pero el trabajo se está moviendo. Lo principal es que un parche completo es bastante accesible para usted en el servidor de Revisión de Código, por lo que puede aplicarlo fácilmente a las fuentes de HotSpot y construir el JVM usted mismo con JWarmUp.


Juan Fumero - TornadoVM


Este es un trabajo de investigación de Manchester, pero los autores afirman que el proyecto ya se ha implementado en algunos lugares. También es un complemento para OpenJDK, que hace que sea bastante fácil transferir cierto código Java a GPU, iGPU, FPGA o simplemente paralelizarlo a los núcleos de su procesador. Para compilar en la GPU, utilizan GraalVM en el que construyeron su backend: TornadoJIT. Un método Java escrito correctamente va de forma transparente al dispositivo correspondiente. Es cierto, dicen que la compilación en FPGA puede tomar varias horas, pero si su tarea se considera un mes, entonces por qué no. Algunos puntos de referencia (por ejemplo, la transformada discreta de Fourier) son más de cien veces más rápidos que Java, lo que se espera en principio. El proyecto está completamente cargado en GitHub , donde también puede encontrar publicaciones científicas sobre el tema.


Maurizio Cimadomore - Deconstruyendo Panamá


La misma canción: un proyecto de larga data, cada presentación en la cumbre, hace un año todo parecía bastante listo, pero aún no había lanzamiento. Resultó que desde entonces el enfoque ha cambiado.


La idea del proyecto es una interoperación mejorada con código nativo. Todos saben lo doloroso que es usar JNI. Realmente duele. El proyecto de Panamá anula este dolor: el uso de clases Java de jextract se genera a partir de los archivos * .h de la biblioteca nativa, que son bastante convenientes de usar llamando a métodos nativos. En el lado C / C ++, no tiene que escribir una sola línea. Además, todo se volvió mucho más rápido: la sobrecarga en llamadas a Java-> native y native-> Java cayó a veces. ¿Qué más podrías querer?


Hay un problema que existe desde hace bastante tiempo: la transferencia de matrices de datos a código nativo. Hasta ahora, el método recomendado es DirectByteBuffer, que tiene muchos problemas. Uno de los más serios es la vida útil no administrada (el búfer desaparecerá cuando el recolector de basura recoja el objeto Java apropiado). Debido a este y otros problemas, las personas usan Inseguro, que, con la debida diligencia, puede colocar fácilmente toda la máquina virtual.


Esto significa que necesita un nuevo acceso a la memoria normal fuera del montón de Java. Asignación, accesores estructurados, eliminación explícita. Accesores estructurados: para que no tenga que calcular los desplazamientos usted mismo si necesita escribir, por ejemplo, struct { byte x; int y; }[5] struct { byte x; int y; }[5] struct { byte x; int y; }[5] . En cambio, una vez describe el diseño de esta estructura y luego hace, por ejemplo, VarHandle , que puede leer todo x saltando sobre y . En este caso, por supuesto, siempre debe haber una verificación de bordes, como en las matrices Java normales. Además, debería prohibirse el acceso a un área ya cerrada. Y esto resulta ser una tarea no trivial si queremos mantener el rendimiento en el nivel inseguro y permitir el acceso desde múltiples hilos. En resumen, mira el video, muy interesante.


Taller: Vladimir Kozlov - Proyecto Metrópolis


El proyecto Metropolis combina todos los intentos de reescribir partes de la JVM en Java. Su parte principal hoy es el compilador Graal. En los últimos años, se ha desarrollado muy bien y ya se habla de un reemplazo completo para el envejecimiento C2. Solía ​​haber un problema de arranque: el grial comenzó lentamente, porque él mismo tuvo que ser compilado o interpretado JIT. Luego apareció la compilación AOT (sí, el objetivo principal del proyecto de compilación AOT es el arranque del propio grial). Pero con AOT, el grial consume una porción decente del montón de una aplicación Java que realmente no quiere compartir su montón. Ahora hemos aprendido a convertir el grial en una biblioteca nativa usando Graal Native Image, que finalmente nos permitió aislar el compilador del montón general. Con el rendimiento máximo del código compilado por el grial, todavía hay problemas en algunos puntos de referencia. Por ejemplo, el grial va a la zaga de C2 en intrínseca y vectorización. Sin embargo, gracias al poderoso análisis de escape y alineación, simplemente rompe C2 en el código funcional, donde se crean muchos objetos inmutables y muchas funciones pequeñas. Si escribe en la roca y aún no usa el grial, corra para usar. Además, en las últimas versiones de JDK es bastante trivial hacer un par de claves, todo ya está en el kit.


31 de julio


Kevin Bourrillion - Anotaciones de nulidad para Java


Kevin anunció un nuevo proyecto, pero pidió no hablar públicamente y no publicar una grabación de su discurso en YouTube. Lo siento mucho , .


Dmitry Petrashko — Gradual typing for Ruby at Scale with Sorbet


Sorbet (!) Ruby, Ruby . , Stripe Ruby , , . , .


Lightning Talks


- . Remi Forax , , . , :



, - , .


Erik Meijer — Differentiable Programming


ML AI , . , Facebook — getafix , --, , . . , , . , , .


. . OpenJDK Committer Workshop.

Source: https://habr.com/ru/post/462101/


All Articles