¿El.NET CLR JIT compila todos los métodos, cada vez?
(4)
El tiempo de ejecución de .NET siempre compila el código JIT antes de la ejecución. Por lo tanto, nunca se interpreta.
Puede encontrar algunas lecturas más interesantes en CLR Design Choices con Anders Hejlsberg . Especialmente la parte:
Leí que Microsoft decidió que IL siempre se compilará, nunca se interpretará. ¿Cómo la información de tipo de codificación en las instrucciones ayuda a los intérpretes a funcionar de manera más eficiente?
Anders Hejlsberg: Si un intérprete puede hacer ciegamente lo que dicen las instrucciones sin tener que rastrear lo que está en la parte superior de la pila, puede ir más rápido. Cuando ve un iadd, por ejemplo, el intérprete no tiene que averiguar primero qué tipo de complemento es, sabe que es un agregado de números enteros. Suponiendo que alguien ya haya verificado que la pila se ve correcta, es seguro dedicar algo de tiempo allí, y eso le importa a un intérprete. En nuestro caso, sin embargo, nunca tuvimos la intención de apuntar a un escenario interpretado con el CLR. Teníamos la intención de siempre JIT [compilación Just-in-time], y para los fines del JIT, teníamos que rastrear la información de tipo de todos modos. Como ya tenemos la información de tipo, en realidad no nos compra nada para ponerlo en las instrucciones.
Bill Venners: muchas JVM modernas [máquinas virtuales Java] realizan una optimización adaptativa, donde comienzan interpretando códigos de bytes. Hacen un perfil de la aplicación a medida que se ejecuta para encontrar el 10% al 20% del código que se ejecuta del 80% al 90% del tiempo, luego lo compilan a nativo. Sin embargo, no necesariamente compilan esos bytecodes justo a tiempo. El intérprete todavía puede ejecutar los códigos de bytes de un método, ya que se están compilando en forma nativa y se optimizan en segundo plano. Cuando el código nativo está listo, puede reemplazar los códigos de bytes. Al no apuntar a un escenario interpretado, ¿ha descartado por completo ese enfoque de ejecución en un CLR?
Anders Hejlsberg: No, no lo hemos descartado por completo. Todavía podemos interpretar. Simplemente no estamos optimizados para la interpretación. No estamos optimizados para escribir el intérprete de mayor rendimiento que solo interpretará. No creo que nadie vuelva a hacer eso. Para un decodificador hace 10 años, eso podría haber sido interesante. Pero ya no es interesante. Las tecnologías JIT han llegado al punto en que puede tener múltiples estrategias JIT posibles. Incluso puede imaginar el uso de un JIT rápido que se rompe rápidamente, y luego, cuando descubrimos que estamos ejecutando un método particular todo el tiempo, usamos otro JIT que pasa un poco más de tiempo y hace un mejor trabajo de optimización. Hay mucho más que puedes hacer JIT.
No lo creo, y creo que nunca debería.
¿Cómo podría el JIT saber cuántas veces se llamaría un método en particular? ¿No sería la frecuencia de la interpretación un factor en la decisión?
También me preguntaría qué tan bien un compilador JIT podría analizar una función para determinar si sería mejor o no la interpretación sin interpretar la función en sí. Y dado ese hecho (que al menos una pasada del método ha tenido lugar), ¿no sería mejor simplemente compilar cada método para reducir la sobrecarga de intentar determinar qué métodos se compilan en primer lugar?
Nota: esta respuesta está en un contexto "por ejecución". El código normalmente es JITted cada vez que ejecuta el programa. Usar ngen o .NET Native también cambia esa historia ...
A diferencia de HotSpot, el CLR JIT siempre compila exactamente una vez por ejecución. Nunca interpreta, y nunca compila con una optimización más pesada que antes basada en el uso real.
Esto puede cambiar, por supuesto, pero ha sido así desde v1 y no espero que cambie pronto.
La ventaja es que hace que el JIT sea mucho más sencillo: no es necesario considerar el código "antiguo" que ya se está ejecutando, deshacer las optimizaciones basadas en premisas que ya no son válidas, etc.
Un punto a favor de .NET es que la mayoría de los lenguajes CLR hacen que los métodos no sean virtuales de forma predeterminada, lo que significa que se puede hacer mucho más en línea. HotSpot puede incluir un método en línea hasta que se invalide por primera vez, momento en el que deshace la optimización (o hace algunas cosas inteligentes en algunos casos para usar condicionalmente el código en línea, según el tipo real). Con menos métodos virtuales de los que preocuparse, .NET puede ignorar en gran medida el dolor de no poder en línea nada virtual.
EDITAR: Lo anterior describe el marco de escritorio. El Marco Compacto arroja código nativo cuando lo desea, JITting nuevamente según sea necesario. Sin embargo, esto todavía no es como la optimización adaptativa de HotSpots.
Aparentemente, el micro framework no funciona con JIT, sino que interpreta el código. Esto tiene sentido para dispositivos muy restringidos. (No puedo decir que sé mucho sobre el micro marco).
Será agradable ver algunos JIT basados en trazas en el futuro para dispositivos con poca memoria. Principalmente interpretaría, encontraría puntos calientes y los convertiría en ensamblador y los almacenaría en caché. Creo que esto es lo que Google hace con su JIT de Android y Microsoft Research tiene un proyecto de investigación en curso para el JIT basado en trazas.
Encontré un artículo, SPUR: un compilador JIT basado en trazas para CIL ... ¿Tal vez algo de esto llegará al CLR algún día?