2012-01-23 14 views
6

Ok, he leído varias discusiones sobre las diferencias entre JIT y los intérpretes habilitados para JIT, y por qué JIT generalmente aumenta el rendimiento.Do Not JIT y non-JIT habilitado Intérpretes Ultimately Produce Machine Code

Sin embargo, mi pregunta es:

En última instancia, no un no-JIT habilitado intérprete tiene que convertir el código de bytes (línea por línea) en código máquina/nativo para ser ejecutado, al igual que un compilador JIT hará ? He visto publicaciones y libros de texto que dicen que sí y publicaciones que dicen que no. El último argumento es que el intérprete/JVM ejecuta este bytecode directamente sin interacción con el código máquina/nativo.

Si intérpretes no JIT hacen girar cada línea en código máquina, parece que los principales beneficios de JIT son ...

  1. La inteligencia de almacenamiento en caché o bien todos (JIT normal) o encuentra con frecuencia (zona activa/optimización adaptativa) partes del bytecode para que el paso de compilación de código de máquina no sea necesario cada vez.

  2. Cualquier optimización que los compiladores JIT puedan realizar al traducir bytecode en código de máquina.

¿Es eso exacto? Parece que hay poca diferencia (aparte de una posible optimización, o bloques JITting vs línea por línea) entre la traducción de bytecode a código máquina a través de intérpretes que no son JIT ni JIT.

Gracias de antemano.

Respuesta

8

Un intérprete que no es JIT no convierte el bytecode en código de máquina. Se puede imaginar el funcionamiento de un código de bytes no JIT intérprete algo como esto (Voy a usar un pseudocódigo similar a Java):

int[] bytecodes = { ... }; 
int ip  = 0; // instruction pointer 
while(true) { 
    int code = bytecodes[ip]; 
    switch(code) { 
    case 0; 
     // do something 
     ip += 1; break; 
    case 1: 
     // do something else 
     ip += 1; break; 
    // and so on... 
    } 
} 

Así, por cada código de bytes se ejecuta, el intérprete tiene que recuperar el código, encienda su valor para decidir qué hacer e incrementar su "puntero de instrucción" antes de pasar a la siguiente iteración.

Con un JIT, toda esa sobrecarga se reduciría a nada. Solo tomaría el contenido de las ramas de cambio apropiadas (las partes que dicen "// hacer algo"), las unirá en la memoria y ejecutará un salto al comienzo de la primera. No se requeriría ningún "puntero de instrucción" de software, solo el puntero de instrucción de hardware de la CPU. Sin recuperación de bytecodes de la memoria y encendido de sus valores tampoco.

Escribir una máquina virtual no es difícil (si no tiene que ser un rendimiento extremadamente alto), y puede ser un ejercicio interesante. Hice una vez para un proyecto integrado donde el código del programa tenía que ser muy compacto.

+0

¡Gracias por la respuesta! Estaba leyendo el libro "Comenzando con juegos y gráficos en C++", y menciona "El intérprete traduce cada instrucción de alto nivel en su instrucción de lenguaje máquina equivalente y la ejecuta de inmediato". Ahí es donde comenzó la confusión. Sin embargo, estaba hablando de intérpretes en general. –

+0

Un intérprete "traduce" comandos de alto nivel al lenguaje de la máquina cambiando/saltando a una sección de lenguaje de máquina que implementa la operación representada por el comando de alto nivel. –

0

Hace décadas, parecía haber una creencia generalizada de que los compiladores convertían un programa completo en código máquina, mientras que los intérpretes traducían una declaración en código máquina, la ejecutaban, descartaban, traducían la siguiente, etc. Esa noción era 99% incorrecto, pero había dos pequeños núcleos de verdad. En algunos microprocesadores, algunas instrucciones requerían el uso de direcciones que se especificaban en el código. Por ejemplo, en el 8080, había una instrucción para leer o escribir una dirección de E/S especificada 0x00-0xFF, pero no había instrucciones para leer o escribir una dirección de E/S especificada en un registro. Era común para los intérpretes de idiomas, si el código de usuario hacía algo como "fuera 123,45", almacenar en tres bytes de memoria las instrucciones "fuera 7Bh/ret", cargar el acumulador con 2Dh, y hacer una llamada al primero de esas instrucciones.En esa situación, el intérprete estaría produciendo una instrucción de código de máquina para ejecutar la instrucción interpretada. Tal generación de código, sin embargo, se limitaba principalmente a cosas como las instrucciones IN y OUT.

Muchos intérpretes comunes de Microsoft BASIC para el 6502 (y tal vez el 8080 también) hicieron un uso algo más extenso del código almacenado en RAM, pero el código almacenado en RAM no dependía significativamente del programa que se estaba ejecutando; la mayoría de la rutina de RAM no cambiaría durante la ejecución del programa, pero la dirección de la siguiente instrucción se mantuvo en línea como parte de la rutina, lo que permite el uso de una instrucción "LDA" de modo absoluto, que salvó al menos un ciclo cada byte buscar