Compilazione JIT C # e .NET


87

Sono diventato un po 'confuso sui dettagli di come funziona il compilatore JIT. So che C # compila fino a IL. La prima volta che viene eseguito è JIT. Questo implica che venga tradotto in codice nativo? Il runtime .NET (come macchina virtuale?) Interagisce con il codice JIT? So che è ingenuo, ma mi sono davvero confuso. La mia impressione è sempre stata che gli assembly non siano interpretati da .NET Runtime ma non capisco i dettagli dell'interazione.

Risposte:


84

Sì, il JIT'ing codice IL implica la traduzione dell'IL in istruzioni macchina native.

Sì, il runtime .NET interagisce con il codice macchina nativo di JIT, nel senso che il runtime possiede i blocchi di memoria occupati dal codice macchina nativo, le chiamate runtime nel codice macchina nativo, ecc.

È corretto affermare che il runtime .NET non interpreta il codice IL negli assembly.

Quello che succede è quando l'esecuzione raggiunge una funzione o un blocco di codice (come, una clausola else di un blocco if) che non è stato ancora compilato JIT in codice macchina nativo, JIT'r viene invocato per compilare quel blocco di IL in codice macchina nativo . Al termine, l'esecuzione del programma inserisce il codice macchina appena emesso per eseguire la logica del programma. Se durante l'esecuzione di tale codice macchina nativo, l'esecuzione raggiunge una chiamata di funzione a una funzione che non è stata ancora compilata in codice macchina, JIT'r viene invocato per compilare quella funzione "just in time". E così via.

JIT'r non compila necessariamente tutta la logica del corpo di una funzione nel codice macchina in una volta. Se la funzione ha istruzioni if, i blocchi di istruzioni delle clausole if o else potrebbero non essere compilati JIT finché l'esecuzione non passa effettivamente attraverso quel blocco. I percorsi del codice che non sono stati eseguiti rimangono nel formato IL finché non vengono eseguiti.

Il codice macchina nativo compilato viene mantenuto in memoria in modo che possa essere riutilizzato la prossima volta che viene eseguita quella sezione di codice. La seconda volta che chiami una funzione, verrà eseguita più velocemente della prima volta che la chiami perché nessun passaggio JIT è necessario la seconda volta.

In .NET desktop, il codice macchina nativo viene mantenuto in memoria per la durata dell'appdomain. In .NET CF, il codice macchina nativo può essere gettato via se l'applicazione sta esaurendo la memoria. Sarà JIT compilato di nuovo dal codice IL originale la prossima volta che l'esecuzione passa attraverso quel codice.


14
Correzione pedante minore - 'Il JIT'r non compila necessariamente tutta la logica del corpo di una funzione' - In un'implementazione teorica che potrebbe essere il caso, ma nelle implementazioni esistenti del runtime .NET, il compilatore JIT compilerà tutto metodi tutti in una volta.
Paul Alexander

18
Per quanto riguarda il motivo per cui ciò viene fatto, è principalmente perché un compilatore non jit non può presumere che determinate ottimizzazioni siano disponibili su una determinata piattaforma di destinazione. Le uniche opzioni sono la compilazione al minimo comune denominatore o, più comunemente, la compilazione di più versioni, ciascuna destinata alla propria piattaforma. JIT elimina questo svantaggio perché la traduzione finale in codice macchina viene eseguita sulla macchina di destinazione, dove il compilatore sa quali ottimizzazioni sono disponibili.
Chris Shain

1
Chris, anche se JIT sa quali ottimizzazioni sono disponibili, non ha tempo per applicare ottimizzazioni significative. Probabilmente NGEN è più potente.
Grigory

2
@Grigory Sì, NGEN ha il lusso di tempo che il compilatore JIT non lo fa, ma ci sono molte decisioni codegen che JIT può prendere per migliorare le prestazioni del codice compilato che non richiedono molto tempo per capire. Ad esempio, il compilatore JIT può selezionare i set di istruzioni per abbinare al meglio l'hardware disponibile trovato in fase di esecuzione senza aggiungere tempo significativo alla fase di compilazione JIT. Non penso che .NET JITter lo faccia in modo significativo, ma è possibile.
dthorpe

24

Il codice viene "compilato" nel Microsoft Intermediate Language, che è simile al formato assembly.

Quando si fa doppio clic su un eseguibile, viene caricato Windows mscoree.dllche quindi configura l'ambiente CLR e avvia il codice del programma. Il compilatore JIT inizia a leggere il codice MSIL nel programma e compila dinamicamente il codice in istruzioni x86, che la CPU può eseguire.


2

Descriverò la compilazione del codice IL nelle istruzioni native della CPU tramite l'esempio di seguito.

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

Principalmente CLR conosce tutti i dettagli sul tipo e su quale metodo viene chiamato da quel tipo, ciò è dovuto ai metadati.

Quando CLR inizia a eseguire IL nell'istruzione CPU nativa, quella volta CLR alloca strutture di dati interne per ogni tipo a cui fa riferimento il codice di Main.

Nel nostro caso abbiamo solo un tipo Console, quindi CLR allocherà una struttura dati interna. Tramite quella struttura interna, gestiremo l'accesso alle tipologie referenziate.

All'interno di quella struttura dati, CLR ha voci su tutti i metodi definiti da quel tipo. Ogni voce contiene l'indirizzo in cui è possibile trovare l'implementazione del metodo.

Quando si inizializza questa struttura, CLR imposta ogni voce in FUNZIONE non documentata contenuta all'interno di CLR stesso. E come puoi immaginare, questa FUNZIONE è ciò che chiamiamo compilatore JIT.

Complessivamente potresti considerare JIT Compiler come una funzione CLR, che compila IL in istruzioni native della CPU. Lascia che ti mostri in dettaglio come sarà questo processo nel nostro esempio.

1.Quando Main effettua la sua prima chiamata a WriteLine, viene chiamata la funzione JITCompiler.

2. La funzione del compilatore JIT sa quale metodo viene chiamato e quale tipo definisce questo metodo.

3.Quindi Jit Compiler ricerca l'assembly in cui è stato definito quel tipo e ottiene il codice IL per il metodo definito da quel tipo nel nostro caso il codice IL del metodo WriteLine.

4. Il compilatore JIT alloca il blocco di memoria DYNAMIC , dopodiché JIT verifica e compila il codice IL nel codice CPU nativo e salva quel codice CPU in quel blocco di memoria.

5.Quindi il compilatore JIT torna alla voce della struttura dati interna e sostituisce l'indirizzo (che fa principalmente riferimento all'implementazione del codice IL di WriteLine) con l'indirizzo nuovo blocco di memoria creato dinamicamente, che contiene le istruzioni CPU native di WriteLine.

6.Infine, la funzione JIT Compiler salta al codice nel blocco di memoria ed esegue il codice nativo del metodo di scrittura.

7.Dopo l'esecuzione di WriteLine, il codice ritorna al Mains'code che continua l'esecuzione normalmente.


1

.NET utilizza un linguaggio intermedio chiamato MSIL, a volte abbreviato in IL. Il compilatore legge il codice sorgente e produce MSIL. Quando si esegue il programma, il compilatore .NET Just In Time (JIT) legge il codice MSIL e produce un'applicazione eseguibile in memoria. Non vedrai accadere nulla di tutto questo, ma è una buona idea sapere cosa sta succedendo dietro le quinte.


0

.NET Framework utilizza l'ambiente CLR per produrre MSIL (Microsoft Intermediate Language), chiamato anche IL. Il compilatore legge il tuo codice sorgente e quando costruisci / compili il tuo progetto, genera MSIL. Ora, quando finalmente esegui il tuo progetto, .NET JIT ( Just-in-time Compiler ) entra in azione. JIT legge il codice MSIL e produce codice nativo (che sono istruzioni x86) che possono essere facilmente eseguite dalla CPU.JIT legge tutte le istruzioni MSIL e le esegue riga per riga.

Se sei interessato a vedere cosa succede dietro le quinte, è già stato risposto. Si prega di seguire - Qui

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.