c# - Compilador JIT contra compiladores fuera de línea
.net c++ (10)
¿Hay escenarios en los que el compilador JIT es más rápido que otros compiladores como C ++?
¿Crees que en el futuro el compilador JIT solo verá optimizaciones menores, características pero seguirá un rendimiento similar, o habrá avances que lo harán infinitamente superior a otros compiladores?
Parece que el paradigma de núcleos múltiples tiene alguna promesa, pero no es magia universal.
¿Alguna idea?
Básicamente, los compiladores de JIT tienen la oportunidad de crear un perfil de la aplicación que se está ejecutando, y hacer algunas sugerencias basadas en esa información. Los compiladores "fuera de línea" no podrán determinar con qué frecuencia una rama salta y con qué frecuencia se produce, sin insertar un código especial, solicitan al desarrollador que ejecute el programa, lo ponga a prueba y recompile.
¿Por qué importa esto?
//code before
if(errorCondition)
{
//error handling
}
//code after
Se convierte en algo así como:
//code before
Branch if not error to Code After
//error handling
Code After:
//Code After
Y los procesadores x86 no predecirían un salto condicional en ausencia de información de la unidad de predicción de bifurcación. Eso significa que predice la ejecución del código de manejo de errores, y el procesador tendrá que vaciar la tubería cuando se dé cuenta de que la condición de error no ocurrió.
Un compilador JIT podría ver eso, e insertar una sugerencia para la rama, para que la CPU pueda predecir en la dirección correcta. Por supuesto, los compiladores fuera de línea pueden estructurar el código de una manera que evitaría el error de escritura, pero si alguna vez tiene que mirar el conjunto, es posible que no le guste saltar por todas partes ...
JIT tiene ventajas, pero no veo que se haga cargo por completo. Los compiladores convencionales pueden dedicar más tiempo a la optimización, mientras que un JIT necesita encontrar un equilibrio entre demasiada optimización (tomar más tiempo de lo que ahorra la optimización) y muy poco (tomar demasiado tiempo en ejecución directa).
La respuesta obvia es usar cada uno donde sea superior. Los JIT pueden aprovechar el perfilado en tiempo de ejecución más fácilmente que los optimizadores convencionales (aunque existen compiladores que pueden tomar los perfiles de tiempo de ejecución como entrada para la optimización de la guía) y generalmente pueden hacer más optimizaciones específicas de la CPU (una vez más, los compiladores hacen esto, pero si espera ejecutar el ejecutable en diferentes sistemas, no pueden aprovecharlo al máximo). Los compiladores convencionales pueden pasar más tiempo y hacerlo de diferentes maneras.
Por lo tanto, el sistema de idiomas del futuro tendrá buenos compiladores de optimización que emitirán código ejecutable diseñado para ser utilizado por buenos compiladores JIT de optimización. (Esto también es, para muchas personas, el sistema de lenguaje del presente.) (El sistema de lenguaje del futuro también admitirá todo, desde los scripts modernos de Python / VB hasta el crujido de números de alta velocidad más feo).
Como con muchas cosas, esto fue prefigurado por Lisp. Hace bastante tiempo, algunos sistemas Lisp (no pueden decir muchos, no ha habido tantas implementaciones de Common Lisp) interpretaban las funciones de Lisp compilándolas sobre la marcha. Las expresiones S de Lisp (en qué código está escrito) son descripciones bastante sencillas de árboles de análisis sintáctico, por lo que la compilación podría ser bastante rápida. Mientras tanto, un compilador Lisp de optimización podría descifrar el código donde el rendimiento era realmente importante antes de tiempo.
Los compiladores de JIT tienen más datos que pueden usar para influir en las optimizaciones. Por supuesto, alguien tiene que escribir código para usar esa información, así que no es tan simple.
Sí, ciertamente hay tales escenarios.
- La compilación de JIT puede usar el perfil de tiempo de ejecución para optimizar casos específicos en función de la medición de las características de lo que el código realmente está haciendo en este momento, y puede recompilar el código "activo" según sea necesario. Eso no es teórico; Java''s HotSpot realmente hace esto.
- JITters puede optimizar la configuración específica de CPU y memoria en uso en el hardware real donde el programa se está ejecutando. Por ejemplo, muchas aplicaciones .NET se ejecutarán en código de 32 bits o de 64 bits, dependiendo de dónde estén JITted. En el hardware de 64 bits usarán más registros, memoria y un mejor conjunto de instrucciones.
- Las llamadas a métodos virtuales dentro de un ciclo cerrado pueden reemplazarse por llamadas estáticas basadas en el conocimiento del tiempo de ejecución del tipo de referencia.
Creo que habrá avances en el futuro. En particular, creo que la combinación de la compilación de JIT y el tipado dinámico mejorará significativamente. Ya estamos viendo esto en el espacio de JavaScript con V8 de Chrome y TraceMonkey. Espero ver otras mejoras de magnitud similar en un futuro no muy lejano. Esto es importante porque incluso los llamados lenguajes "estáticos" tienden a tener una serie de características dinámicas.
Sí, los compiladores JIT pueden producir un código de máquina más rápido optimizado para el entorno actual. Pero prácticamente los programas VM son más lentos que los nativos porque JITing consume tiempo (más Optimización == más tiempo), y para muchos métodos JITing puede consumir más tiempo que ejecutarlos. Y es por eso que GAC se presenta en .NET
Un efecto secundario para JITing es el gran consumo de memoria. Sin embargo, eso no está relacionado con la velocidad de cálculo, sino que puede ralentizar la ejecución del programa completo, ya que el consumo de memoria grande aumenta la probabilidad de que su código se pagine en el almacenamiento secundario.
Disculpa me por mi mal inglés.
Otra cosa que se salteó en esta conversación es que cuando JIT un pedazo de código se puede compilar a un lugar libre en la memoria. En un lenguaje como C ++ si el archivo DLL se basa de tal manera que ese trozo de memoria no está disponible, tendrá que pasar por el costoso proceso de rebase. Es más rápido JIT codificar en una dirección no utilizada luego volver a establecer una base de datos compilada en un espacio de memoria libre. Para empeorar las cosas, una DLL rebaseada ya no se puede compartir. (vea http://msdn.microsoft.com/en-us/magazine/cc163610.aspx )
No me han impresionado mucho algunas de las optimizaciones en el código JIT de C # 3.5. Cosas simples como los movimientos de bit que son necesarios para la compresión son terriblemente ineficientes (se negó a almacenar en caché los valores en un registro de CPU y en su lugar fue a la memoria para cada operación). No sé por qué haría esto, pero hace una gran diferencia y no hay nada que pueda hacer al respecto.
Personalmente, creo que una buena solución sería un nivel de optimización (1-100) que podría establecer para decirle al compilador de JIT cuánto tiempo cree que debería gastar optimizando su código. La única otra solución sería un compilador AOT (Ahead of Time) y luego perderá muchas de las ventajas del código JIT.
Muchas personas respondieron que tal vez estoy rozando (quizás tengo el extremo equivocado) pero para mí son dos cosas diferentes:
AFAIK no hay nada que te detenga JIT''compilado c ++ por ejemplo proyecto código máquina Dynamo JIT:
http://arstechnica.com/reviews/1q00/dynamo/dynamo-1.html
Y realmente proporcionó mejoras de velocidad en ciertas circunstancias.
Compilar código, en el sentido de un compilador de C ++, significa tomar un código escrito en un idioma y convertirlo en un conjunto de instrucciones (o en algunos casos otro idioma para compilarlo de nuevo) que puede ser ejecutado por algún tipo de dispositivo lógico.
por ejemplo, c ++ compilación a ensamblado (creo que ;-) o c # compilando a IL o compilación de Java a código de bytes
JIT es un proceso que ocurre durante la ejecución. La máquina que ejecuta el código lo analiza para ver si puede mejorarlo. Java y C # son capaces de hacer uso de ambos a medida que el compilador prepara los comandos para la máquina virtual y, a continuación, la máquina virtual tiene la oportunidad, al menos, de tener otra oportunidad para optimizarla.
Algunos programas no se compilan, se interpretan, lo que significa que la máquina que los ejecuta lee el código exacto que usted escribió. Estas máquinas tienen la oportunidad de hacer JIT, pero recuerden que también pueden compilarse estáticamente, potencialmente ser un proveedor externo en formas que el diseñador original del lenguaje nunca pensó.
Entonces, para responder a su pregunta, no creo que JIT reemplace los compiladores estáticos. Creo que siempre habrá (siempre que haya programación al menos) un lugar para tomar una representación de un programa y convertirlo en un conjunto de instrucciones para una máquina de algún tipo. (potencialmente optimizando mientras lo hace)
SIN EMBARGO, creo que JIT puede convertirse en una parte más importante de la historia, a medida que evolucionan el tiempo de ejecución Java y el tiempo de ejecución de .NET. Estoy seguro de que JIT mejorará y me darán cosas como Project Dynamo. Supongo que hay margen para que el hardware tome JIT también, para que todo lo que hace su procesador se vuelva a optimizar según el entorno de tiempo de ejecución.
Los compiladores de JIT conocen más sobre los sistemas que los compiladores estáticos. Agregar multitheading sobre la marcha específico para la máquina puede ser una mejora de velocidad gigantesca una vez que lo ponen en funcionamiento.
Los compiladores JIT en general tienen un poco de latencia de inicio donde la primera ejecución del programa / código puede ser mucho más lenta que el código precompilado. La desventaja del arranque en frío.
Otra gran ventaja de la compilación de JIT es que el compilador se puede actualizar después de que se haya construido su programa y obtener nuevos trucos de compilación sin necesidad de una nueva implementación completa del programa.
Una ventaja de JITing que aún no se ha mencionado es que es posible que un programa defina un número infinito de tipos genéricos. Por ejemplo:
interface IGenericAction { bool Act<T>(); }
struct Blah<T>
{
public static void ActUpon(IGenericAction action)
{
if (action.Act<T>())
Blah<Blah<T>>.ActUpon(action);
}
}
Llamar a Blah<Int32>.ActUpon(act)
llamará a act.Act<Int32>()
. Si ese método devuelve verdadero, llamará a Blah<Blah<Int32>>.ActUpon(act)
, que a su vez llamará a act.Act<Blah<Int32>>()
. Si eso devuelve verdadero, se realizarán más llamadas con un tipo aún más profundamente anidado. Generar código para todos los métodos de ActUpon
que podrían llamarse sería imposible, pero afortunadamente no es necesario. Los tipos no necesitan ser generados hasta que sean usados. Si action<Blah<...50 levels deep...>>.Act()
devuelve false, luego Blah<Blah<...50 levels deep...>>.ActUpon
no llamará a Blah<Blah<...51 levels deep...>>.ActUpon
y el último tipo no necesitarán ser creados.
Un punto aún no mencionado que favorece a los compiladores "fuera de línea" es que tales compiladores pueden ser útiles para plataformas con pequeñas cantidades de RAM, incluso tan solo dieciséis BYTES. Para estar seguro, todo lo que sea remotamente compatible con PC es capaz de tener (literalmente) millones de veces más RAM que eso, pero creo que pasará un tiempo antes de que uno pueda encontrar una máquina con muchos megas de RAM que cueste menos de $ 0.50 y consuma menos de un milivatio de potencia durante la operación.
Tenga en cuenta que los 16 bytes de RAM no son tan débiles como parece, ya que los chips con una RAM tan pequeña no almacenan código en la memoria RAM: tienen un área de memoria no volátil separada para contener el código (384 bytes es el el más pequeño que conozco). Eso no es mucho, por supuesto, pero es suficiente para permitir que un procesador de $ 0.25 realice funciones que de otra manera requerirían un valor de $ 1.00 en componentes discretos.