Compilación de C # JIT y .NET


86

Me he confundido un poco sobre los detalles de cómo funciona el compilador JIT. Sé que C # se compila en IL. La primera vez que se ejecuta, está JIT. ¿Esto implica que se traduzca a código nativo? ¿El tiempo de ejecución de .NET (como una máquina virtual?) ¿Interactúa con el código JIT? Sé que esto es ingenuo, pero realmente me he confundido. Mi impresión siempre ha sido que los ensamblados no son interpretados por .NET Runtime pero no entiendo los detalles de la interacción.

Respuestas:


83

Sí, el código IL de JIT implica traducir el IL en instrucciones nativas de la máquina.

Sí, el tiempo de ejecución de .NET interactúa con el código de máquina nativo editado por JIT, en el sentido de que el tiempo de ejecución es propietario de los bloques de memoria ocupados por el código de máquina nativo, el tiempo de ejecución llama al código de máquina nativo, etc.

Tiene razón en que el tiempo de ejecución de .NET no interpreta el código IL en sus ensamblados.

Lo que sucede es que cuando la ejecución alcanza una función o un bloque de código (como una cláusula else de un bloque if) que aún no ha sido compilado con JIT en código de máquina nativo, se invoca el JIT'r para compilar ese bloque de IL en código de máquina nativo. . Una vez hecho esto, la ejecución del programa ingresa el código de máquina recién emitido para ejecutar la lógica del programa. Si mientras se ejecuta esa ejecución de código de máquina nativo llega una llamada de función a una función que aún no ha sido compilada en código de máquina, se invoca el JIT'r para compilar esa función "justo a tiempo". Y así.

El JIT'r no necesariamente compila toda la lógica del cuerpo de una función en código de máquina a la vez. Si la función tiene sentencias if, es posible que los bloques de sentencias de las cláusulas if o else no se compilen con JIT hasta que la ejecución pase a través de ese bloque. Las rutas de código que no se han ejecutado permanecen en forma IL hasta que se ejecutan.

El código de máquina nativo compilado se mantiene en la memoria para que pueda volver a utilizarse la próxima vez que se ejecute esa sección de código. La segunda vez que llame a una función, se ejecutará más rápido que la primera vez que la llame porque no es necesario ningún paso JIT la segunda vez.

En .NET de escritorio, el código de máquina nativo se mantiene en la memoria durante la vida útil del dominio de la aplicación. En .NET CF, el código de máquina nativo puede desecharse si la aplicación se está quedando sin memoria. Se volverá a compilar JIT a partir del código IL original la próxima vez que la ejecución pase por ese código.


14
Corrección pedante menor - 'El JIT'r no necesariamente compila toda la lógica de un cuerpo de función' - En una implementación teórica que puede ser el caso, pero en las implementaciones existentes del tiempo de ejecución .NET, el compilador JIT compilará todo métodos todos a la vez.
Paul Alexander

18
En cuanto a por qué se hace esto, se debe principalmente a que un compilador no jit no puede asumir que ciertas optimizaciones están disponibles en una plataforma de destino determinada. Las únicas opciones son compilar con el mínimo común denominador o, más comúnmente, compilar varias versiones, cada una dirigida a su propia plataforma. JIT elimina esa desventaja porque la traducción final al código de máquina se realiza en la máquina de destino, donde el compilador sabe qué optimizaciones están disponibles.
Chris Shain

1
Chris, aunque JIT sabe qué optimizaciones están disponibles, no tiene tiempo para aplicar optimizaciones significativas. Probablemente NGEN sea más potente.
Grigory

2
@Grigory Sí, NGEN tiene el lujo de tiempo que el compilador JIT no tiene, pero hay muchas decisiones de codegen que el JIT puede tomar para mejorar el rendimiento del código compilado que no requiere mucho tiempo para resolver. Por ejemplo, el compilador JIT puede seleccionar conjuntos de instrucciones para que coincidan mejor con el hardware disponible que se encuentra en tiempo de ejecución sin agregar tiempo significativo al paso de compilación JIT. No creo que .NET JITter haga esto de manera significativa, pero es posible.
dthorpe

24

El código se "compila" en el lenguaje intermedio de Microsoft, que es similar al formato ensamblador.

Cuando hace doble clic en un ejecutable, Windows carga, mscoree.dllque luego configura el entorno CLR e inicia el código de su programa. El compilador JIT comienza a leer el código MSIL en su programa y compila dinámicamente el código en instrucciones x86, que la CPU puede ejecutar.


1

.NET usa un lenguaje intermedio llamado MSIL, a veces abreviado como IL. El compilador lee su código fuente y produce MSIL. Cuando ejecuta el programa, el compilador .NET Just In Time (JIT) lee su código MSIL y genera una aplicación ejecutable en la memoria. No verá nada de esto suceder, pero es una buena idea saber qué está pasando detrás de escena.


1

Describiré la compilación del código IL en instrucciones nativas de CPU a través del ejemplo siguiente.

public class Example 
{
    static void Main() 
    {
        Console.WriteLine("Hey IL!!!");
    }
}

Principalmente CLR conoce todos los detalles sobre el tipo y qué método se llama desde ese tipo, esto se debe a los metadatos.

Cuando CLR comienza a ejecutar IL en una instrucción de CPU nativa, ese tiempo CLR asigna estructuras de datos internas para cada tipo al que hace referencia el código de Main.

En nuestro caso, solo tenemos un tipo de consola, por lo que CLR asignará una estructura de datos interna a través de esa estructura interna, administraremos el acceso a los tipos referenciados.

dentro de esa estructura de datos, CLR tiene entradas sobre todos los métodos definidos por ese tipo. Cada entrada contiene la dirección donde se puede encontrar la implementación del método.

Al inicializar esta conjuntos estructura CLR cada entrada de indocumentado FUNCIÓN contenido dentro de CLR itself.And como se puede adivinar esta FUNCIÓN es lo que llamamos JIT Compiler.

En general, podría considerar el compilador JIT como una función CLR que compila IL en instrucciones nativas de la CPU. Permítanme mostrarles en detalle cómo será este proceso en nuestro ejemplo.

1.Cuando Main hace su primera llamada a WriteLine, se llama a la función JITCompiler.

2. La función del compilador JIT sabe qué método se está llamando y qué tipo define este método.

Luego, Jit Compiler busca el ensamblado donde se definió ese tipo y obtiene el código IL para el método definido por ese tipo en nuestro caso, el código IL del método WriteLine.

El compilador JIT asigna el bloque de memoria DYNAMIC , después de que JIT verifique y compile el código IL en el código de la CPU nativo y guarde ese código de la CPU en ese bloque de memoria.

Luego, el compilador JIT vuelve a la entrada de la estructura de datos interna y reemplaza la dirección (que principalmente hace referencia a la implementación del código IL de WriteLine) con la dirección del nuevo bloque de memoria creado dinámicamente que contiene instrucciones nativas de CPU de WriteLine.

6. Finalmente, la función del compilador JIT salta al código en el bloque de memoria. Este código es la implementación del método WriteLine.

7.Después de la implementación de WriteLine, el código regresa al código de la red principal, que continúa la ejecución con normalidad.


0

.NET Framework utiliza CLR Environment para producir MSIL (Microsoft Intermediate Language), también llamado IL. El compilador lee tu código fuente y cuando construyes / compilas tu proyecto, genera MSIL. Ahora, cuando finalmente ejecute su proyecto, el .NET JIT ( compilador Just-in-time ) entra en acción. JIT lee su código MSIL y produce código nativo (que son instrucciones x86) que puede ser ejecutado fácilmente por la CPU. JIT lee todas las instrucciones MSIL y las ejecuta línea por línea.

Si está interesado en ver qué sucede detrás de escena, ya ha sido respondido. Por favor siga - Aquí

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.