¿En qué se diferencia un compilador JIT de un compilador ordinario?
22
Ha habido mucha publicidad sobre los compiladores JIT para lenguajes como Java, Ruby y Python. ¿En qué se diferencian los compiladores JIT de los compiladores C / C ++, y por qué los compiladores escritos para Java, Ruby o Python se denominan compiladores JIT, mientras que los compiladores C / C ++ se llaman compiladores?
Los compiladores JIT compilan el código sobre la marcha, justo antes de su ejecución o incluso cuando ya se están ejecutando. De esta manera, la VM donde se ejecuta el código puede verificar patrones en la ejecución del código para permitir optimizaciones que solo serían posibles con información de tiempo de ejecución. Además, si la máquina virtual decide que la versión compilada no es lo suficientemente buena por cualquier razón (por ejemplo, demasiadas fallas en la memoria caché o código que frecuentemente arroja una excepción particular), puede decidir recompilarla de una manera diferente, lo que lleva a una versión mucho más inteligente Compilacion.
Por otro lado, los compiladores C y C ++ tradicionalmente no son JIT. Se compilan en un solo disparo una sola vez en la máquina del desarrollador y luego se produce un ejecutable.
¿Hay compiladores JIT que realizan un seguimiento de los errores de caché y adaptan su estrategia de compilación en consecuencia? @Victor
Martin Berger
@MartinBerger No sé si los compiladores JIT disponibles hacen eso, pero ¿por qué no? A medida que las computadoras son más poderosas y la informática se desarrolla, las personas pueden aplicar más optimizaciones al programa. Por ejemplo, cuando nació Java, es muy lento, tal vez 20 veces en comparación con los compilados, pero ahora el rendimiento no es tan lento y puede ser comparable a algunos compiladores
phuclv
He escuchado esto en alguna publicación de blog hace mucho tiempo. El compilador puede compilar de manera diferente dependiendo de la CPU actual. Por ejemplo, puede vectorizar algún código automáticamente si detecta que la CPU admite SSE / AVX. Cuando las extensiones SIMD más recientes están disponibles, puede compilarse con las más nuevas, por lo que el programador no necesita cambiar nada. O si puede organizar las operaciones / datos para aprovechar el caché más grande o reducir la pérdida de caché
phuclv
@ LưuVĩnhPhúc Chào! Me alegra creer eso, pero la diferencia es que, por ejemplo, el tipo de CPU o el tamaño de la memoria caché son algo estático que no cambia a lo largo del cálculo, por lo que también podría hacerlo fácilmente un compilador estático. La memoria caché falla OTOH son muy dinámicas.
Martin Berger
12
JIT es la abreviatura de compilador justo a tiempo, y el nombre es misson: durante el tiempo de ejecución, determina optimizaciones de código que valen la pena y las aplica. No reemplaza a los compiladores habituales, pero son parte de los intérpretes. Tenga en cuenta que los lenguajes como Java que usan código intermedio tienen ambos : un compilador normal para la traducción de código fuente a intermedio, y un JIT incluido en el intérprete para aumentar el rendimiento.
Ciertamente, los compiladores "clásicos" pueden realizar optimizaciones de código, pero tenga en cuenta la diferencia principal: los compiladores JIT tienen acceso a los datos en tiempo de ejecución. Esta es una gran ventaja; explotarlo adecuadamente puede ser difícil, obviamente.
Considere, por ejemplo, un código como este:
m(a : String, b : String, k : Int) {
val c : Int;
switch (k) {
case 0 : { c = 7; break; }
...
case 17 : { c = complicatedMethod(k, a+b); break; }
}
return a.length + b.length - c + 2*k;
}
Un compilador normal no puede hacer mucho al respecto. Sin embargo, un compilador JIT puede detectar que msolo se llama k==0por alguna razón (cosas como esa pueden suceder a medida que el código cambia con el tiempo); luego puede crear una versión más pequeña del código (y compilarlo en código nativo, aunque considero que esto es un punto menor, conceptualmente):
En este punto, probablemente incluso alineará la llamada al método, ya que es trivial ahora.
Aparentemente, el Sol descartó la mayoría de las optimizaciones que javacsolía hacer en Java 6; Me han dicho que esas optimizaciones dificultaron que JIT hiciera mucho, y al final el código compilado ingenuamente fue más rápido. Imagínate.
Por cierto, en presencia de borrado de tipo (por ejemplo, genéricos en Java) JIT está realmente en desventaja para los tipos de wrt.
Raphael
Por lo tanto, el tiempo de ejecución es más complicado que eso para un lenguaje compilado porque el entorno de tiempo de ejecución debe recopilar datos para optimizarlo.
saadtaame
2
En muchos casos, un JIT no sería capaz de reemplazar mcon una versión que no comprobó kya que sería incapaz de demostrar que msería nunca se puede llamar con un no-cero k, pero incluso sin ser capaz de demostrar que podía reemplazar con static miss_count; if (k==0) return a.length+b.length-7; else if (miss_count++ < 16) { ... unoptimized code for m ...} else { ... consider other optimizations...}.
supercat
1
Estoy de acuerdo con @supercat y creo que su ejemplo es bastante engañoso. Solo si k=0siempre , lo que significa que no es una función de la entrada o el entorno, es seguro abandonar la prueba, pero determinar esto requiere un análisis estático, que es muy costoso y, por lo tanto, solo asequible en el momento de la compilación. Un JIT puede ganar cuando una ruta a través de un bloque de código se toma con mucha más frecuencia que otras, y una versión del código especializada para esta ruta sería mucho más rápida. Pero el JIT aún emitirá una prueba para verificar si se aplica la ruta rápida , y tomar la "ruta lenta" si no.
j_random_hacker
Un ejemplo: una función toma un Base* pparámetro y llama a funciones virtuales a través de él; El análisis de tiempo de ejecución muestra que el objeto real señalado siempre (o casi siempre) parece ser de Derived1tipo. El JIT podría producir una nueva versión de la función con llamadas a los Derived1métodos resueltas estáticamente (o incluso en línea) . Este código iría precedido por un condicional que verifica si pel puntero vtable apunta a la Derived1tabla esperada ; de lo contrario, salta a la versión original de la función con sus llamadas a métodos más lentas resueltas dinámicamente.
JIT es la abreviatura de compilador justo a tiempo, y el nombre es misson: durante el tiempo de ejecución, determina optimizaciones de código que valen la pena y las aplica. No reemplaza a los compiladores habituales, pero son parte de los intérpretes. Tenga en cuenta que los lenguajes como Java que usan código intermedio tienen ambos : un compilador normal para la traducción de código fuente a intermedio, y un JIT incluido en el intérprete para aumentar el rendimiento.
Ciertamente, los compiladores "clásicos" pueden realizar optimizaciones de código, pero tenga en cuenta la diferencia principal: los compiladores JIT tienen acceso a los datos en tiempo de ejecución. Esta es una gran ventaja; explotarlo adecuadamente puede ser difícil, obviamente.
Considere, por ejemplo, un código como este:
Un compilador normal no puede hacer mucho al respecto. Sin embargo, un compilador JIT puede detectar que
m
solo se llamak==0
por alguna razón (cosas como esa pueden suceder a medida que el código cambia con el tiempo); luego puede crear una versión más pequeña del código (y compilarlo en código nativo, aunque considero que esto es un punto menor, conceptualmente):En este punto, probablemente incluso alineará la llamada al método, ya que es trivial ahora.
Aparentemente, el Sol descartó la mayoría de las optimizaciones que
javac
solía hacer en Java 6; Me han dicho que esas optimizaciones dificultaron que JIT hiciera mucho, y al final el código compilado ingenuamente fue más rápido. Imagínate.fuente
m
con una versión que no comprobók
ya que sería incapaz de demostrar quem
sería nunca se puede llamar con un no-cerok
, pero incluso sin ser capaz de demostrar que podía reemplazar constatic miss_count; if (k==0) return a.length+b.length-7; else if (miss_count++ < 16) { ... unoptimized code for
m...} else { ... consider other optimizations...}
.k=0
siempre , lo que significa que no es una función de la entrada o el entorno, es seguro abandonar la prueba, pero determinar esto requiere un análisis estático, que es muy costoso y, por lo tanto, solo asequible en el momento de la compilación. Un JIT puede ganar cuando una ruta a través de un bloque de código se toma con mucha más frecuencia que otras, y una versión del código especializada para esta ruta sería mucho más rápida. Pero el JIT aún emitirá una prueba para verificar si se aplica la ruta rápida , y tomar la "ruta lenta" si no.Base* p
parámetro y llama a funciones virtuales a través de él; El análisis de tiempo de ejecución muestra que el objeto real señalado siempre (o casi siempre) parece ser deDerived1
tipo. El JIT podría producir una nueva versión de la función con llamadas a losDerived1
métodos resueltas estáticamente (o incluso en línea) . Este código iría precedido por un condicional que verifica sip
el puntero vtable apunta a laDerived1
tabla esperada ; de lo contrario, salta a la versión original de la función con sus llamadas a métodos más lentas resueltas dinámicamente.