stringtranslate.com

Recopilació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 informático ) 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 es más comúnmente la traducción del 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 está ejecutando e identifica partes del código donde la aceleración obtenida de 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 para la traducción a código 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 la 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 la optimización adaptativa, como la recompilación dinámica y las 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 tiempo de ejecución puede manejar tipos de datos enlazados tardíamente y hacer cumplir las garantías de seguridad.

Historia

El primer compilador JIT publicado se atribuye generalmente al trabajo en LISP de John McCarthy en 1960. [4] En su artículo seminal 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, lo que evita la necesidad de guardar la salida del compilador en tarjetas perforadas [5] (aunque esto se conocería con más precisión como un " 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 al código IBM 7094 en el Sistema de tiempo compartido compatible . [4] Una técnica influyente para derivar código compilado a partir de la interpretación fue iniciada por James G. Mitchell en 1970, 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 hacía a pedido y el resultado se almacenaba en caché para su uso posterior. Cuando la memoria escaseaba, el sistema borraba parte de este código y lo regeneraba cuando era necesario nuevamente. [2] [9] El lenguaje Self de Sun mejoró estas técnicas considerablemente 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 completamente orientado a objetos.

Sun abandonó Self, pero la investigación se centró en el lenguaje Java. El término "compilación Just-in-time" se tomó prestado del término de fabricación " Just in time " y fue popularizado por Java, con James Gosling utilizando el término a partir de 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 Dynamo de HP era un compilador JIT experimental en el que el formato de "bytecode" y el formato de código de máquina eran el mismo; el sistema optimizaba el código de máquina PA-8000 . [12] Contrariamente a la intuición, esto dio como resultado aumentos de velocidad, en algunos casos del 30%, ya que hacer esto permitió optimizaciones a nivel de código de máquina, por ejemplo, código en línea para un mejor uso de la memoria caché y optimizaciones de llamadas a bibliotecas dinámicas y muchas otras optimizaciones en tiempo de ejecución 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 bytecode, el código fuente se traduce a una representación intermedia conocida como bytecode . El bytecode no es el código de máquina para ningún ordenador en particular, y puede ser portable entre arquitecturas informáticas. El bytecode puede ser interpretado por, o ejecutado en una máquina virtual . El compilador JIT lee los bytecodes 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 compilarlo.

Por el contrario, una máquina virtual interpretada tradicional simplemente interpretará el bytecode, generalmente con un rendimiento mucho menor. Algunos intérpretes incluso interpretan el código fuente, sin el paso de compilar primero en bytecode, con un rendimiento incluso 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 uno en el que se puede utilizar el compilador 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 bytecode: 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 de bytecode a código de máquina es mucho más rápida que la compilación desde la fuente. El bytecode implementado es portátil, a diferencia del código nativo. Dado que el entorno de ejecución tiene control sobre la compilación, como el bytecode interpretado, puede ejecutarse en un entorno seguro. Los compiladores de bytecode a código de máquina son más fáciles de escribir, porque el compilador de bytecode 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 solo son factibles en tiempo de ejecución: [16] [17]

  1. La compilación se puede optimizar para la CPU de destino y el modelo del 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 varias versiones de partes del código dentro de un solo 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 obtener un rendimiento óptimo. Sin embargo, algunos compiladores estáticos también pueden tomar información de perfil como entrada.
  3. El sistema puede realizar optimizaciones de código globales (por ejemplo, la incorporación de funciones de biblioteca en línea) sin perder las ventajas de la vinculación dinámica y sin los costos indirectos inherentes a los compiladores y enlazadores estáticos. En concreto, al realizar sustituciones en línea globales, un proceso de compilación estática puede necesitar comprobaciones en tiempo de ejecución y garantizar que se producirá una llamada virtual si la clase real del objeto anula el método incorporado en línea, y las comprobaciones de condiciones de contorno en los accesos a matrices pueden necesitar procesarse 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. Si bien esto es posible con lenguajes recolectados 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 del caché.

Debido a que un JIT debe renderizar y ejecutar una imagen binaria nativa en tiempo de ejecución, los JIT de código de máquina verdaderos necesitan plataformas que permitan ejecutar datos 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 también. Sin embargo, un tipo especial de "JIT" puede potencialmente no apuntar a la arquitectura de CPU de la máquina física, sino más bien a un bytecode de VM optimizado donde prevalecen las limitaciones del código de máquina sin procesar, especialmente cuando la VM de ese bytecode eventualmente aprovecha un JIT para convertirlo en código nativo. [18]

Actuación

JIT provoca un retraso leve o notable en la ejecución inicial de una aplicación, debido al tiempo que se tarda en cargar y compilar el código de entrada. A veces, este retraso se denomina "retraso del tiempo de inicio" o "tiempo de calentamiento". En general, cuanto 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 la clase rt.jar para la máquina virtual Java (JVM) es de 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 la interpretación y la compilación JIT. El código de la aplicación se interpreta inicialmente, pero la JVM supervisa qué secuencias de bytecode se ejecutan con frecuencia y las traduce a código de máquina para su ejecución directa en el hardware. Para el bytecode que se ejecuta solo unas pocas veces, esto ahorra tiempo de compilación y reduce la latencia inicial; para el bytecode que se ejecuta con frecuencia, se utiliza la compilación JIT para ejecutarse a alta velocidad, después de una fase inicial de interpretación lenta. Además, dado que un programa pasa la mayor parte del tiempo ejecutando una minoría de su código, el tiempo de compilación reducido es significativo. 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 el 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 extensas para maximizar el rendimiento una vez que la aplicación se está ejecutando sacrificando el tiempo de inicio. Otros compiladores de Java justo a tiempo han utilizado una medición del tiempo de ejecución de la cantidad de veces que se ha ejecutado un método combinado con el tamaño del código de bytes de un método como una heurística para decidir cuándo compilar. [21] Otro utiliza la cantidad de veces ejecutadas combinada 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 ejecución corta que en las de ejecución larga. [23]

Native Image Generator (Ngen) de Microsoft es otro enfoque para reducir el retraso inicial. [24] Ngen precompila (o "pre-JIT") el código de bytes en una imagen de lenguaje intermedio común en código nativo de máquina. Como resultado, no se necesita 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 justo después de la instalación. La pre-compilación proporciona una forma 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 compilado 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 creación 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 (ahead-of-time) con un compilador JIT ( Excelsior JET ) o un intérprete ( GNU Compiler for Java ).

La compilación JIT puede no lograr de manera confiable su objetivo, es decir, entrar en 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 usan comúnmente como objetivos de optimización, ejecutándolos repetidamente dentro de una sola ejecución de proceso. [28] En Linux , encontraron que entre el 8,7% y el 9,6% de las ejecuciones de procesos no alcanzaron 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 una máquina virtual específica que ejecutaba un punto de referencia específico no vieron consistentemente una no degradación del rendimiento en estado estable en múltiples ejecuciones (es decir, al menos una ejecución no alcanzó un estado estable o vio un rendimiento reducido en el estado estable). Incluso cuando se alcanzó un estado estable mejorado, a veces se necesitaron muchos cientos de iteraciones. [29] Traini et al. (2022) en cambio se centró en la máquina virtual HotSpot pero con una gama mucho más amplia de puntos de referencia, [30] encontrando 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, por lo que plantea desafíos de seguridad y posibles vulnerabilidades de seguridad.

La implementación de la compilación JIT consiste en compilar el código fuente o el código de bytes en código de máquina y ejecutarlo. Esto se hace generalmente directamente en la memoria: el compilador JIT envía el código de máquina directamente a 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 : no se puede ejecutar memoria arbitraria, ya que de lo contrario existe un potencial agujero de seguridad. Por lo 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 se haya marcado como de solo lectura, ya que la memoria escribible/ejecutable es un agujero de seguridad (ver 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 utilizan la compilación JIT para la pulverización de montón : la memoria resultante es luego ejecutable, lo que permite una explotación 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 ciertas 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 a 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 a alta velocidad, incluidas 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 a código de bytes o a código de máquina. La compilación JIT también se utiliza en algunos emuladores, para traducir 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 la compilación AOT a bytecode ( código de máquina virtual ), conocida como compilación de bytecode , y luego tener la compilación JIT a código de máquina (compilación dinámica), en lugar de la interpretación del bytecode. 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 solo se compila una parte del programa, hay significativamente menos retraso que si se compilara todo el programa antes de la ejecución.

Véase también

Notas

  1. ^ Los compiladores Ahead-of-Time también pueden apuntar a microarquitecturas específicas, pero la diferencia entre AOT y JIT en ese asunto es la portabilidad. Un JIT puede renderizar código adaptado a la CPU que se está ejecutando en ese momento en tiempo de ejecución, mientras que un AOT, en lugar de optimizar para un subconjunto generalizado de arquitecturas, debe conocer la CPU de destino de antemano: dicho código no solo puede no funcionar bien en otros tipos de CPU, sino que puede ser completamente inestable.

Referencias

  1. ^ Lenguajes, compiladores y sistemas de ejecución, Universidad de Michigan, Ciencias de la computación e ingeniería , 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ág. 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ág. 98–99.
  8. ^ Mitchell, JG (1970). "El diseño y construcción de sistemas de programación interactiva flexibles y eficientes". {{cite journal}}: Requiere citar revista |journal=( ayuda )
  9. ^ Deutsch, 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 . pp. 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". research.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: A Transparent Dynamic Optimization System". 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. ^ John Jannotti. "El dinamo de HP". Ars Technica . 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, Louis. "Just in Time Compilation" (PDF) . Universidad de Columbia . Archivado desde el original (PDF) el 3 de mayo de 2018.
  17. ^ "¿Cuáles son las ventajas de la compilación JIT frente a la AOT?". Stack Overflow . 21 de enero de 2010.
  18. ^ "Compilar un lenguaje basado en JIT para Webassembly". Desbordamiento de pila . Consultado el 4 de diciembre de 2018 .
  19. ^ Haase, Chet (mayo de 2007). "Consumer JRE: una tecnología Java más eficiente y eficaz". Sun Microsystems . 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. ^ Schilling, Jonathan L. (febrero de 2003). "Las heurísticas más simples pueden ser las mejores en los compiladores JIT de Java" (PDF) . SIGPLAN Notices . 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 13.º Taller internacional sobre lenguajes y compiladores para computación paralela - Documentos revisados , págs. 49-67, 10-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). "A Survey of Adaptive Optimization in Virtual Machines" (PDF) . Actas del IEEE . 92 (2): 449–466. Archivado desde el original (PDF) el 29 de junio de 2016.
  26. ^ Barrett y otros. 2017, pág. 3.
  27. ^ Traini y otros, 2022, pág. 1.
  28. ^ Barrett y otros. 2017, págs. 5-6.
  29. ^ Barrett y otros. 2017, págs. 12-13.
  30. ^ Traini y col. 2022, pág. 17-23.
  31. ^ Traini y col. 2022, pág. 26-29.
  32. ^ "Cómo implementar 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