stringtranslate.com

Compilación justo a tiempo

En informática , la compilación justo a tiempo ( JIT ) (también traducción dinámica o compilaciones en tiempo de ejecución ) [1] es la compilación (de código de computadora ) durante la ejecución de un programa (en tiempo de ejecución ) en lugar de antes de la ejecución. [2] Esto puede consistir en la traducción del código fuente , pero más comúnmente es una traducción de código de bytes a código de máquina , que luego se ejecuta directamente. Un sistema que implementa un compilador JIT generalmente analiza continuamente el código que se ejecuta e identifica partes del código donde la aceleración obtenida con la compilación o recompilación superaría la sobrecarga de compilar ese código.

La compilación JIT es una combinación de los dos enfoques tradicionales de traducción a código de máquina (compilación anticipada (AOT) e interpretación ) y combina algunas ventajas y desventajas de ambos. [2] En términos generales, la compilación JIT combina la velocidad del código compilado con la flexibilidad de interpretación, con la sobrecarga de un intérprete y la sobrecarga adicional de compilar y vincular (no solo interpretar). La compilación JIT es una forma de compilación dinámica y permite una optimización adaptativa , como la recompilación dinámica y aceleraciones específicas de la microarquitectura . [nb 1] [3] La interpretación y la compilación JIT son particularmente adecuadas para lenguajes de programación dinámicos , ya que el sistema de ejecución puede manejar tipos de datos enlazados en tiempo de ejecución y hacer cumplir las garantías de seguridad.

Historia

El primer compilador JIT publicado generalmente se atribuye al trabajo sobre LISP de John McCarthy en 1960. [4] En su artículo fundamental Funciones recursivas de expresiones simbólicas y su cálculo por máquina, Parte I , menciona funciones que se traducen durante el tiempo de ejecución, ahorrando así la necesidad de guardar la salida del compilador en tarjetas perforadas [5] (aunque esto se conocería más exactamente como " sistema de compilación y listo "). Otro ejemplo temprano fue el de Ken Thompson , quien en 1968 dio una de las primeras aplicaciones de expresiones regulares , aquí para la coincidencia de patrones en el editor de texto QED . [6] Para mayor velocidad, Thompson implementó la coincidencia de expresiones regulares mediante JITing con el código IBM 7094 en el Sistema de tiempo compartido compatible . [4] James G. Mitchell fue pionero en 1970 en una técnica influyente para derivar código compilado a partir de la interpretación, que implementó para el lenguaje experimental LC² . [7] [8]

Smalltalk (c. 1983) fue pionero en nuevos aspectos de las compilaciones JIT. Por ejemplo, la traducción a código de máquina se realizó bajo demanda y el resultado se almacenó en caché para su uso posterior. Cuando la memoria escaseaba, el sistema eliminaba parte de este código y lo regeneraba cuando era necesario nuevamente. [2] [9] El lenguaje Self de Sun mejoró ampliamente estas técnicas y en un momento fue el sistema Smalltalk más rápido del mundo, logrando hasta la mitad de la velocidad de C optimizado [10] pero con un lenguaje totalmente orientado a objetos.

Sun abandonó Self, pero la investigación se centró en el lenguaje Java. El término "compilación justo a tiempo" fue tomado del término de fabricación " Justo a tiempo " y popularizado por Java, y James Gosling utilizó el término en 1993. [11] Actualmente, la mayoría de las implementaciones de la máquina virtual Java utilizan JITing. , ya que HotSpot se basa en esta base de investigación y la utiliza ampliamente.

El proyecto HP Dynamo era un compilador JIT experimental donde el formato de "código de bytes" y el formato de código de máquina eran los mismos; El sistema optimizó el código de máquina PA-8000 . [12] Contrariamente a la intuición, esto resultó en aceleraciones, en algunos casos del 30%, ya que hacer esto permitió optimizaciones a nivel de código de máquina, por ejemplo, insertar código para un mejor uso de la caché y optimizaciones de llamadas a bibliotecas dinámicas y muchos otros tiempos de ejecución. optimizaciones que los compiladores convencionales no pueden intentar. [13] [14]

En noviembre de 2020, PHP 8.0 introdujo un compilador JIT. [15]

Diseño

En un sistema compilado con código de bytes, el código fuente se traduce a una representación intermedia conocida como código de bytes . Bytecode no es el código de máquina de ninguna computadora en particular y puede ser portátil entre arquitecturas de computadora. El código de bytes puede luego ser interpretado o ejecutado en una máquina virtual . El compilador JIT lee los códigos de bytes en muchas secciones (o en su totalidad, rara vez) y los compila dinámicamente en código de máquina para que el programa pueda ejecutarse más rápido. Esto se puede hacer por archivo, por función o incluso en cualquier fragmento de código arbitrario; el código se puede compilar cuando está a punto de ejecutarse (de ahí el nombre "justo a tiempo") y luego almacenarse en caché y reutilizarse más tarde sin necesidad de volver a compilarse.

Por el contrario, una máquina virtual interpretada tradicional simplemente interpretará el código de bytes, generalmente con un rendimiento mucho menor. Algunos intérpretes incluso interpretan el código fuente, sin el paso de compilarlo primero en código de bytes, con un rendimiento aún peor. El código compilado estáticamente o el código nativo se compila antes de la implementación. Un entorno de compilación dinámico es aquel en el que el compilador se puede utilizar durante la ejecución. Un objetivo común del uso de técnicas JIT es alcanzar o superar el rendimiento de la compilación estática , manteniendo al mismo tiempo las ventajas de la interpretación del código de bytes: gran parte del "trabajo pesado" de analizar el código fuente original y realizar una optimización básica a menudo se maneja en el momento de la compilación. antes de la implementación: la compilación del código de bytes al código de máquina es mucho más rápida que la compilación desde el código fuente. El código de bytes implementado es portátil, a diferencia del código nativo. Dado que el tiempo de ejecución tiene control sobre la compilación, como el código de bytes interpretado, puede ejecutarse en un entorno limitado seguro. Los compiladores de código de bytes a código de máquina son más fáciles de escribir porque el compilador de código de bytes portátil ya ha hecho gran parte del trabajo.

El código JIT generalmente ofrece un rendimiento mucho mejor que los intérpretes. Además, en algunos casos puede ofrecer un mejor rendimiento que la compilación estática, ya que muchas optimizaciones sólo son factibles en tiempo de ejecución: [16] [17]

  1. La compilación se puede optimizar para la CPU de destino y el modelo de sistema operativo donde se ejecuta la aplicación. Por ejemplo, JIT puede elegir instrucciones de CPU vectoriales SSE2 cuando detecta que la CPU las admite. Para obtener este nivel de especificidad de optimización con un compilador estático, se debe compilar un binario para cada plataforma/arquitectura prevista o incluir múltiples versiones de partes del código dentro de un único binario.
  2. El sistema puede recopilar estadísticas sobre cómo se ejecuta realmente el programa en el entorno en el que se encuentra y puede reorganizarlo y recompilarlo para lograr un rendimiento óptimo. Sin embargo, algunos compiladores estáticos también pueden tomar información del perfil como entrada.
  3. El sistema puede realizar optimizaciones globales de código (por ejemplo, incorporación de funciones de biblioteca) sin perder las ventajas del enlace dinámico y sin los gastos generales inherentes a los compiladores y enlazadores estáticos. Específicamente, al realizar sustituciones en línea globales, un proceso de compilación estática puede necesitar verificaciones en tiempo de ejecución y garantizar que se produzca una llamada virtual si la clase real del objeto anula el método en línea, y es posible que sea necesario procesar verificaciones de condiciones de límite en los accesos a la matriz. dentro de bucles. Con la compilación justo a tiempo, en muchos casos este procesamiento se puede sacar de los bucles, lo que a menudo proporciona grandes aumentos de velocidad.
  4. Aunque esto es posible con lenguajes de recolección de basura compilados estáticamente, un sistema de código de bytes puede reorganizar más fácilmente el código ejecutado para una mejor utilización de la caché.

Debido a que un JIT debe representar y ejecutar una imagen binaria nativa en tiempo de ejecución, los verdaderos JIT de código de máquina necesitan plataformas que permitan que los datos se ejecuten en tiempo de ejecución, lo que hace imposible el uso de dichos JIT en una máquina basada en la arquitectura Harvard ; Lo mismo puede decirse de ciertos sistemas operativos y máquinas virtuales. Sin embargo, es posible que un tipo especial de "JIT" no esté dirigido a la arquitectura de CPU de la máquina física, sino más bien a un código de bytes de VM optimizado donde prevalecen las limitaciones en el código de máquina sin procesar, especialmente cuando la VM de ese código de bytes finalmente aprovecha un JIT para convertirlo en código nativo. [18]

Actuación

JIT provoca un retraso de leve a notable en la ejecución inicial de una aplicación, debido al tiempo necesario para cargar y compilar el código de entrada. A veces, este retraso se denomina "retraso en el tiempo de inicio" o "tiempo de calentamiento". En general, cuanta más optimización realice JIT, mejor será el código que generará, pero el retraso inicial también aumentará. Por lo tanto, un compilador JIT tiene que hacer un equilibrio entre el tiempo de compilación y la calidad del código que espera generar. El tiempo de inicio puede incluir un aumento de las operaciones vinculadas a IO además de la compilación JIT: por ejemplo, el archivo de datos de clase rt.jar para la máquina virtual Java (JVM) ocupa 40 MB y la JVM debe buscar una gran cantidad de datos en este archivo contextualmente enorme. . [19]

Una posible optimización, utilizada por la máquina virtual Java HotSpot de Sun , es combinar interpretación y compilación JIT. El código de la aplicación se interpreta inicialmente, pero la JVM monitorea qué secuencias de código de bytes se ejecutan con frecuencia y las traduce a código de máquina para su ejecución directa en el hardware. Para el código de bytes que se ejecuta sólo unas pocas veces, esto ahorra tiempo de compilación y reduce la latencia inicial; Para el código de bytes ejecutado con frecuencia, se utiliza la compilación JIT para ejecutarlo a alta velocidad, después de una fase inicial de interpretación lenta. Además, dado que un programa dedica la mayor parte del tiempo a ejecutar una minoría de su código, la reducción del tiempo de compilación es significativa. Finalmente, durante la interpretación inicial del código, se pueden recopilar estadísticas de ejecución antes de la compilación, lo que ayuda a realizar una mejor optimización. [20]

La compensación correcta puede variar según las circunstancias. Por ejemplo, la máquina virtual Java de Sun tiene dos modos principales: cliente y servidor. En modo cliente, se realiza una compilación y optimización mínimas para reducir el tiempo de inicio. En el modo servidor, se realiza una compilación y optimización exhaustivas para maximizar el rendimiento una vez que la aplicación se está ejecutando sacrificando el tiempo de inicio. Otros compiladores justo a tiempo de Java han utilizado una medición del tiempo de ejecución del número de veces que se ha ejecutado un método combinada con el tamaño del código de bytes de un método como heurística para decidir cuándo compilar. [21] Otro más utiliza el número de veces ejecutadas combinado con la detección de bucles. [22] En general, es mucho más difícil predecir con precisión qué métodos optimizar en aplicaciones de corta duración que en aplicaciones de larga duración. [23]

Native Image Generator (Ngen) de Microsoft es otro enfoque para reducir el retraso inicial. [24] Ngen precompila (o "pre-JIT") código de bytes en una imagen de lenguaje intermedio común en código nativo de máquina. Como resultado, no se necesita ninguna compilación en tiempo de ejecución. .NET Framework 2.0 incluido con Visual Studio 2005 ejecuta Ngen en todas las DLL de la biblioteca de Microsoft inmediatamente después de la instalación. El pre-jitting proporciona una manera de mejorar el tiempo de inicio. Sin embargo, la calidad del código que genera puede no ser tan buena como la del código JIT, por las mismas razones por las que el código compilado estáticamente, sin optimización guiada por perfiles , no puede ser tan bueno como el código compilado JIT en el caso extremo: la falta de datos de perfiles para impulsar, por ejemplo, el almacenamiento en caché en línea. [25]

También existen implementaciones de Java que combinan un compilador AOT (anticipado) con un compilador JIT ( Excelsior JET ) o un intérprete ( GNU Compiler for Java ).

Es posible que la compilación JIT no logre de manera confiable su objetivo, es decir, ingresar a un estado estable de rendimiento mejorado después de un breve período de calentamiento inicial. [26] [27] En ocho máquinas virtuales diferentes, Barrett et al. (2017) midieron seis microbenchmarks ampliamente utilizados que los implementadores de máquinas virtuales utilizan comúnmente como objetivos de optimización, ejecutándolos repetidamente dentro de una única ejecución de proceso. [28] En Linux , encontraron que entre el 8,7% y el 9,6% de las ejecuciones de procesos no lograron alcanzar un estado estable de rendimiento, entre el 16,7% y el 17,9% entraron en un estado estable de rendimiento reducido después de un período de calentamiento, y el 56,5% de los emparejamientos de un proceso específico La máquina virtual que ejecutaba un punto de referencia específico no logró ver consistentemente una no degradación del rendimiento en estado estable en múltiples ejecuciones (es decir, al menos una ejecución no logró alcanzar un estado estable o vio un rendimiento reducido en el estado estable). Incluso cuando se alcanzaba un estado estacionario mejorado, a veces se necesitaban cientos de iteraciones. [29] Traini et al. (2022) se centraron en cambio en la máquina virtual HotSpot, pero con una gama mucho más amplia de puntos de referencia, [30] y encontraron que el 10,9 % de las ejecuciones de procesos no lograron alcanzar un estado estable de rendimiento y el 43,5 % de los puntos de referencia no alcanzaron consistentemente un estado estable. en múltiples ejecuciones. [31]

Seguridad

La compilación JIT utiliza fundamentalmente datos ejecutables y, por lo tanto, plantea desafíos de seguridad y posibles vulnerabilidades.

La implementación de la compilación JIT consiste en compilar el código fuente o código de bytes en código máquina y ejecutarlo. Esto generalmente se hace directamente en la memoria: el compilador JIT genera el código de máquina directamente en la memoria y lo ejecuta inmediatamente, en lugar de enviarlo al disco y luego invocar el código como un programa separado, como en la compilación previa habitual. En las arquitecturas modernas, esto se topa con un problema debido a la protección del espacio ejecutable : la memoria arbitraria no se puede ejecutar, ya que de lo contrario existe un potencial agujero de seguridad. Por tanto, la memoria debe marcarse como ejecutable; Por razones de seguridad, esto debe hacerse después de que el código se haya escrito en la memoria y marcado como de solo lectura, ya que la memoria grabable/ejecutable es un agujero de seguridad (consulte W^X ). [32] Por ejemplo, el compilador JIT de Firefox para Javascript introdujo esta protección en una versión de lanzamiento con Firefox 46. [33]

La pulverización JIT es una clase de exploits de seguridad informática que utiliza la compilación JIT para la pulverización del montón : la memoria resultante es entonces ejecutable, lo que permite un exploit si la ejecución se puede mover al montón.

Usos

La compilación JIT se puede aplicar a algunos programas o se puede utilizar para determinadas capacidades, en particular capacidades dinámicas como las expresiones regulares . Por ejemplo, un editor de texto puede compilar una expresión regular proporcionada en tiempo de ejecución en código de máquina para permitir una coincidencia más rápida: esto no se puede hacer con anticipación, ya que el patrón solo se proporciona en tiempo de ejecución. Varios entornos de ejecución modernos dependen de la compilación JIT para la ejecución de código de alta velocidad, incluida la mayoría de las implementaciones de Java , junto con .NET de Microsoft . De manera similar, muchas bibliotecas de expresiones regulares cuentan con compilación JIT de expresiones regulares, ya sea en código de bytes o en código de máquina. La compilación JIT también se utiliza en algunos emuladores para traducir el código de máquina de una arquitectura de CPU a otra.

Una implementación común de la compilación JIT es tener primero una compilación AOT en código de bytes ( código de máquina virtual ), conocida como compilación de código de bytes , y luego tener una compilación JIT en código de máquina (compilación dinámica), en lugar de una interpretación del código de bytes. Esto mejora el rendimiento en tiempo de ejecución en comparación con la interpretación, a costa del retraso debido a la compilación. Los compiladores JIT traducen continuamente, al igual que los intérpretes, pero el almacenamiento en caché del código compilado minimiza el retraso en la ejecución futura del mismo código durante una ejecución determinada. Dado que sólo se compila una parte del programa, hay un retraso significativamente menor que si se compilara todo el programa antes de la ejecución.

Ver también

Notas

  1. ^ Los compiladores adelantados también pueden apuntar a microarquitecturas específicas, pero la diferencia entre AOT y JIT en ese sentido es la portabilidad. Un JIT puede generar código adaptado a la CPU que se está ejecutando actualmente en tiempo de ejecución, mientras que un AOT, en lugar de optimizar para un subconjunto generalizado de uarches, debe conocer la CPU de destino de antemano: es posible que dicho código no solo no funcione en otros tipos de CPU, sino que puede ser completamente inestable.

Referencias

  1. ^ Idiomas, compiladores y sistemas de ejecución, Universidad de Michigan, Ingeniería y Ciencias de la Computación , consultado el 15 de marzo de 2018.
  2. ^ abc Aycock 2003.
  3. ^ "¿El JIT aprovecha mi CPU?". WebLog de David Notario . Consultado el 3 de diciembre de 2018 .
  4. ^ ab Aycock 2003, 2. Técnicas de compilación JIT, 2.1 Génesis, p. 98.
  5. ^ McCarthy, J. (abril de 1960). "Funciones recursivas de expresiones simbólicas y su cálculo por máquina, Parte I". Comunicaciones de la ACM . 3 (4): 184-195. CiteSeerX 10.1.1.111.8833 . doi :10.1145/367177.367199. S2CID  1489409. 
  6. ^ Thompson 1968.
  7. ^ Aycock 2003, 2. Técnicas de compilación JIT, 2.2 LC², p. 98–99.
  8. ^ Mitchell, JG (1970). "El diseño y construcción de sistemas de programación interactiva flexibles y eficientes". {{cite journal}}: Citar diario requiere |journal=( ayuda )
  9. ^ Alemán, LP; Schiffman, AM (1984). "Implementación eficiente del sistema Smalltalk-80" (PDF) . Actas del 11º simposio ACM SIGACT-SIGPLAN sobre principios de lenguajes de programación - POPL '84 . págs. 297–302. doi :10.1145/800017.800542. ISBN 0-89791-125-3. S2CID  3045432. Archivado desde el original (PDF) el 18 de junio de 2004.
  10. ^ "97-pep.ps". investigación.sun.com . Archivado desde el original el 24 de noviembre de 2006 . Consultado el 15 de enero de 2022 .
  11. ^ Aycock 2003, 2.14 Java, pág. 107, nota al pie 13.
  12. ^ "Dynamo: un sistema de optimización dinámica transparente". Vasanth Bala, Evelyn Duesterwald, Sanjeev Banerjia. PLDI '00 Actas de la conferencia ACM SIGPLAN 2000 sobre diseño e implementación de lenguajes de programación. páginas 1 a 12. DOI 10.1145/349299.349303. Consultado el 28 de marzo de 2012.
  13. ^ Juan Jannotti. "Dínamo de HP". Ars Técnica . Consultado el 5 de julio de 2013 .
  14. ^ "El proyecto HP Dynamo". Archivado desde el original el 19 de octubre de 2002 . Consultado el 12 de abril de 2016 .{{cite web}}: CS1 maint: unfit URL (link)
  15. ^ Tung, Liam (27 de noviembre de 2020). "El lenguaje de programación PHP 8 ya está disponible: este nuevo compilador JIT apunta a un mejor rendimiento". ZDNet . Consultado el 28 de noviembre de 2020 .
  16. ^ Croce, Luis. "Recopilación Justo a tiempo" (PDF) . Universidad de Colombia . Archivado desde el original (PDF) el 3 de mayo de 2018.
  17. ^ "¿Cuáles son las ventajas de la compilación JIT frente a AOT?". Desbordamiento de pila . 21 de enero de 2010.
  18. ^ "Compilar un idioma basado en JIT en Webassembly". Desbordamiento de pila . Consultado el 4 de diciembre de 2018 .
  19. ^ Haase, Chet (mayo de 2007). "Consumer JRE: tecnología Java más eficiente y eficaz". Microsistemas solares . Consultado el 27 de julio de 2007 .
  20. ^ "La arquitectura del motor de rendimiento Java HotSpot". Oracle.com . Consultado el 5 de julio de 2013 .
  21. ^ Chelín, Jonathan L. (febrero de 2003). "La heurística más simple puede ser la mejor en los compiladores Java JIT" (PDF) . Avisos SIGPLAN . 38 (2): 36–46. doi :10.1145/772970.772975. S2CID  15117148. Archivado desde el original (PDF) el 24 de septiembre de 2015.
  22. ^ Toshio Suganuma, Toshiaki Yasue, Motohiro Kawahito, Hideaki Komatsu, Toshio Nakatani, "Un marco de optimización dinámica para un compilador justo a tiempo de Java", Actas de la 16ª conferencia ACM SIGPLAN sobre programación, sistemas, lenguajes y lenguajes orientados a objetos solicitudes (OOPSLA '01), págs. 180–195, 14–18 de octubre de 2001.
  23. ^ Matthew Arnold, Michael Hind, Barbara G. Ryder, "Un estudio empírico de optimización selectiva", Actas del decimotercer taller internacional sobre lenguajes y compiladores para artículos revisados ​​​​de computación paralela , págs. 49 a 67, 10 al 12 de agosto de 2000 .
  24. ^ "Generador de imágenes nativo (Ngen.exe)". msdn2.microsoft.com. 5 de diciembre de 2006 . Consultado el 5 de julio de 2013 .
  25. ^ Sweeney, Arnold (febrero de 2005). "Una encuesta sobre optimización adaptativa en máquinas virtuales" (PDF) . Actas del IEEE . 92 (2): 449–466. Archivado desde el original (PDF) el 29 de junio de 2016.
  26. ^ Barrett y col. 2017, pág. 3.
  27. ^ Traini y col. 2022, pág. 1.
  28. ^ Barrett y col. 2017, pág. 5-6.
  29. ^ Barrett y col. 2017, pág. 12-13.
  30. ^ Traini y col. 2022, pág. 17-23.
  31. ^ Traini y col. 2022, pág. 26-29.
  32. ^ "Cómo JIT: una introducción", Eli Bendersky, 5 de noviembre de 2013 a las 5:59 am
  33. ^ De Mooij, enero. "Código W^X JIT habilitado en Firefox". Jan De Mooij . Consultado el 11 de mayo de 2016 .

Bibliografía

enlaces externos