Compilação C # JIT e .NET

86

Fiquei um pouco confuso sobre os detalhes de como o compilador JIT funciona. Eu sei que C # compila em IL. Na primeira vez em que é executado, ele é JIT. Isso envolve a tradução para o código nativo? O tempo de execução .NET (como uma máquina virtual?) Interage com o código JIT? Eu sei que isso é ingênuo, mas eu realmente me confundi. Sempre tive a impressão de que os assemblies não são interpretados pelo .NET Runtime, mas não entendo os detalhes da interação.

Steve
fonte

Respostas:

83

Sim, o código JIT'ing IL envolve traduzir o IL em instruções de máquina nativas.

Sim, o runtime .NET interage com o código de máquina nativo JIT'ed, no sentido de que o runtime possui os blocos de memória ocupados pelo código de máquina nativo, o runtime chama o código de máquina nativo, etc.

Você está correto ao dizer que o runtime do .NET não interpreta o código IL em seus assemblies.

O que acontece é que quando a execução atinge uma função ou bloco de código (como uma cláusula else de um bloco if) que ainda não foi compilado JIT em código de máquina nativo, o JIT'r é invocado para compilar esse bloco de IL em código de máquina nativo . Quando isso é feito, a execução do programa insere o código de máquina recém-emitido para executar a lógica do programa. Se, durante a execução do código de máquina nativo, chegar a uma chamada de função para uma função que ainda não foi compilada para o código de máquina, o JIT'r é chamado para compilar essa função "na hora certa". E assim por diante.

O JIT'r não necessariamente compila toda a lógica de um corpo de função em código de máquina de uma vez. Se a função tiver instruções if, os blocos de instrução das cláusulas if ou else não podem ser compilados por JIT até que a execução realmente passe por esse bloco. Os caminhos de código que não foram executados permanecem na forma IL até que sejam executados.

O código de máquina nativo compilado é mantido na memória para que possa ser usado novamente na próxima vez que a seção do código for executada. Na segunda vez que você chamar uma função, ela será executada mais rápido do que na primeira vez, porque nenhuma etapa JIT é necessária na segunda vez.

No desktop .NET, o código de máquina nativo é mantido na memória durante o tempo de vida do appdomain. No .NET CF, o código de máquina nativo pode ser descartado se o aplicativo estiver com pouca memória. Ele será JIT compilado novamente a partir do código IL original na próxima vez que a execução passar por esse código.

dthorpe
fonte
14
Correção pedante menor - 'O JIT'r não necessariamente compila toda a lógica de um corpo de função' - Em uma implementação teórica que pode ser o caso, mas nas implementações existentes do runtime .NET, o compilador JIT compilará todo métodos de uma vez.
Paul Alexander,
18
Quanto ao motivo disso ser feito, é principalmente porque um compilador não-jit não pode assumir que certas otimizações estão disponíveis em qualquer plataforma de destino. As únicas opções são compilar com o menor denominador comum ou, mais comumente, compilar várias versões, cada uma direcionada para sua própria plataforma. O JIT elimina essa desvantagem porque a tradução final para o código de máquina é feita na máquina de destino, onde o compilador sabe quais otimizações estão disponíveis.
Chris Shain,
1
Chris, embora o JIT saiba quais otimizações estão disponíveis, não há tempo para aplicar nenhuma otimização significativa. Provavelmente NGEN é mais poderoso.
Grigory
2
@Grigory Sim, o NGEN tem o luxo do tempo que o compilador JIT não tem, mas existem muitas decisões codegen que o JIT pode tomar para melhorar o desempenho do código compilado que não leva muito tempo para descobrir. Por exemplo, o compilador JIT pode selecionar conjuntos de instruções para melhor corresponder ao hardware disponível encontrado no tempo de execução sem adicionar tempo significativo para a etapa de compilação JIT. Não acho que o .NET JITter faça isso de maneira significativa, mas é possível.
dthorpe
24

O código é "compilado" na Microsoft Intermediate Language, que é semelhante ao formato assembly.

Quando você clica duas vezes em um executável, o Windows é carregado mscoree.dlle, em seguida, configura o ambiente CLR e inicia o código do programa. O compilador JIT começa a ler o código MSIL em seu programa e compila dinamicamente o código em instruções x86, que a CPU pode executar.

user541686
fonte
1

.NET usa uma linguagem intermediária chamada MSIL, às vezes abreviada como IL. O compilador lê seu código-fonte e produz MSIL. Quando você executa o programa, o compilador .NET Just In Time (JIT) lê o código MSIL e produz um aplicativo executável na memória. Você não verá nada disso acontecer, mas é uma boa ideia saber o que está acontecendo nos bastidores.

Gagan
fonte
1

Descreverei a compilação do código IL em instruções nativas da CPU por meio do exemplo abaixo.

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

Principalmente o CLR conhece todos os detalhes sobre o tipo e qual método está sendo chamado desse tipo devido aos metadados.

Quando o CLR começa a executar IL na instrução nativa da CPU, esse tempo CLR aloca estruturas de dados internas para cada tipo referenciado pelo código de Main.

No nosso caso, temos apenas um tipo de Console, portanto, o CLR alocará uma estrutura de dados interna através dessa estrutura interna, gerenciaremos o acesso aos tipos referenciados

dentro dessa estrutura de dados, o CLR tem entradas sobre todos os métodos definidos por aquele tipo. Cada entrada contém o endereço onde a implementação do método pode ser encontrada.

Ao inicializar essa estrutura, o CLR define cada entrada em FUNCTION não documentada contida dentro do próprio CLR. E, como você pode imaginar, essa FUNCTION é o que chamamos de JIT Compiler.

No geral, você pode considerar o JIT Compiler como uma função CLR que compila IL em instruções nativas da CPU. Deixe-me mostrar em detalhes como será esse processo em nosso exemplo.

1.Quando Main faz sua primeira chamada para WriteLine, a função JITCompiler é chamada.

2. A função do compilador JIT sabe qual método está sendo chamado e que tipo define esse método.

3. Em seguida, o Jit Compiler procura o assembly onde definiu aquele tipo e obtém o código IL para o método definido por aquele tipo em nosso caso, código IL do método WriteLine.

4. O compilador JIT aloca o bloco de memória DINÂMICA , depois disso o JIT verifica e compila o código IL no código nativo da CPU e salva esse código da CPU naquele bloco de memória.

5. Então o compilador JIT volta para a entrada da estrutura de dados interna e substitui o endereço (que faz referência principalmente à implementação do código IL de WriteLine) pelo novo bloco de memória criado dinamicamente que contém instruções de CPU nativas de WriteLine.

6.Finalmente, a função JIT Compiler salta para o código no bloco de memória. Este código é a implementação do método WriteLine.

7.Após a implementação do WriteLine, o código retorna ao código Mains, que continua a execução normalmente.

So_oP
fonte
0

O .NET Framework usa o ambiente CLR para produzir o MSIL (Microsoft Intermediate Language), também chamado de IL. O compilador lê seu código-fonte e quando você constrói / compila seu projeto, ele gera MSIL. Agora, quando você finalmente executa seu projeto, o .NET JIT ( Compilador Just-in-time ) entra em ação. JIT lê seu código MSIL e produz código nativo (que são instruções x86) que podem ser facilmente executados pela CPU.JIT lê todas as instruções MSIL e as executa Linha por Linha.

Se você está interessado em ver o que acontece nos bastidores, já foi respondido. Por favor siga - aqui

Dikshit Kathuria
fonte