C # JIT-Kompilierung und .NET

84

Ich bin etwas verwirrt über die Details der Funktionsweise des JIT-Compilers. Ich weiß, dass C # bis IL kompiliert wird. Das erste Mal, wenn es ausgeführt wird, ist es JIT-fähig. Bedeutet dies, dass es in nativen Code übersetzt wird? Interagiert die .NET-Laufzeit (als virtuelle Maschine?) Mit dem JIT-Code? Ich weiß, dass das naiv ist, aber ich habe mich wirklich verwirrt. Mein Eindruck war immer, dass die Assemblys nicht von der .NET Runtime interpretiert werden, aber ich verstehe die Details der Interaktion nicht.

Steve
quelle

Antworten:

82

Ja, beim JIT'ing IL-Code wird die IL in native Maschinenanweisungen übersetzt.

Ja, die .NET-Laufzeit interagiert mit dem nativen JIT-Maschinencode in dem Sinne, dass die Laufzeit die Speicherblöcke besitzt, die vom nativen Maschinencode belegt sind, die Laufzeitaufrufe in den nativen Maschinencode usw.

Sie haben Recht, dass die .NET-Laufzeit den IL-Code in Ihren Assemblys nicht interpretiert.

Wenn die Ausführung eine Funktion oder einen Codeblock erreicht (z. B. eine else-Klausel eines if-Blocks), die noch nicht in nativen Maschinencode kompiliert wurde, wird die JIT'r aufgerufen, um diesen IL-Block in nativen Maschinencode zu kompilieren . Wenn dies erledigt ist, gibt die Programmausführung den frisch ausgegebenen Maschinencode ein, um die Programmlogik auszuführen. Wenn während der Ausführung dieses nativen Maschinencodes die Ausführung eines Funktionsaufrufs für eine Funktion erreicht wird, die noch nicht zu Maschinencode kompiliert wurde, wird die JIT'r aufgerufen, um diese Funktion "just in time" zu kompilieren . Und so weiter.

Das JIT'r kompiliert nicht unbedingt die gesamte Logik eines Funktionskörpers auf einmal in Maschinencode. Wenn die Funktion if-Anweisungen hat, werden die Anweisungsblöcke der if- oder else-Klauseln möglicherweise erst dann JIT-kompiliert, wenn die Ausführung diesen Block tatsächlich durchläuft. Codepfade, die nicht ausgeführt wurden, bleiben in IL-Form, bis sie ausgeführt werden.

Der kompilierte native Maschinencode wird im Speicher gespeichert, damit er bei der nächsten Ausführung dieses Codeabschnitts erneut verwendet werden kann. Wenn Sie eine Funktion zum zweiten Mal aufrufen, wird sie schneller ausgeführt als beim ersten Aufruf, da beim zweiten Mal kein JIT-Schritt erforderlich ist.

In Desktop .NET wird der native Maschinencode für die Lebensdauer der Appdomain gespeichert. In .NET CF wird der native Maschinencode möglicherweise weggeworfen, wenn der Anwendung der Arbeitsspeicher ausgeht. Bei der nächsten Ausführung dieses Codes wird die JIT erneut aus dem ursprünglichen IL-Code kompiliert.

dthorpe
quelle
14
Kleinere pedantische Korrektur - 'Die JIT'r kompiliert nicht unbedingt die gesamte Logik eines Funktionskörpers' - In einer theoretischen Implementierung, die der Fall sein kann, aber in den vorhandenen Implementierungen der .NET-Laufzeit kompiliert der JIT-Compiler das Ganze Methoden auf einmal.
Paul Alexander
18
Der Grund dafür liegt hauptsächlich darin, dass ein Nicht-JIT-Compiler nicht davon ausgehen kann, dass bestimmte Optimierungen auf einer bestimmten Zielplattform verfügbar sind. Die einzige Möglichkeit besteht darin, entweder auf den kleinsten gemeinsamen Nenner zu kompilieren oder häufiger mehrere Versionen zu kompilieren, die jeweils auf ihre eigene Plattform ausgerichtet sind. JIT beseitigt diesen Nachteil, da die endgültige Übersetzung in Maschinencode auf dem Zielcomputer erfolgt, auf dem der Compiler weiß, welche Optimierungen verfügbar sind.
Chris Shain
1
Chris, obwohl JIT weiß, welche Optimierungen verfügbar sind, hat es keine Zeit, signifikante Optimierungen vorzunehmen. Wahrscheinlich ist NGEN mächtiger.
Grigory
2
@Grigory Ja, NGEN hat den Luxus der Zeit, den der JIT-Compiler nicht hat, aber es gibt viele Codegen-Entscheidungen, die die JIT treffen kann, um die Leistung des kompilierten Codes zu verbessern, deren Ermittlung nicht viel Zeit in Anspruch nimmt. Beispielsweise kann der JIT-Compiler Befehlssätze auswählen, die am besten zur verfügbaren Hardware zur Laufzeit passen, ohne dem JIT-Kompilierungsschritt eine erhebliche Zeit hinzuzufügen. Ich glaube nicht, dass der .NET JITter dies in nennenswerter Weise tut, aber es ist möglich.
Dthorpe
23

Code wird in die Microsoft Intermediate Language "kompiliert", die dem Assemblyformat ähnelt.

Wenn Sie auf eine ausführbare Datei doppelklicken, wird Windows geladen, mscoree.dlldas dann die CLR-Umgebung einrichtet und den Code Ihres Programms startet. Der JIT-Compiler beginnt mit dem Lesen des MSIL-Codes in Ihrem Programm und kompiliert den Code dynamisch in x86-Anweisungen, die die CPU ausführen kann.

user541686
quelle
1

.NET verwendet eine Zwischensprache namens MSIL, die manchmal als IL abgekürzt wird. Der Compiler liest Ihren Quellcode und erzeugt MSIL. Wenn Sie das Programm ausführen, liest der JIT-Compiler (.NET Just In Time) Ihren MSIL-Code und erstellt eine ausführbare Anwendung im Speicher. Sie werden nichts davon sehen, aber es ist eine gute Idee zu wissen, was hinter den Kulissen vor sich geht.

Gagan
quelle
1

Ich werde das Kompilieren von IL-Code in native CPU-Anweisungen anhand des folgenden Beispiels beschreiben.

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

In erster Linie kennt CLR alle Details über den Typ und welche Methode von diesem Typ aufgerufen wird, was auf Metadaten zurückzuführen ist.

Wenn die CLR beginnt, IL in der nativen CPU-Anweisung auszuführen, weist die CLR zu diesem Zeitpunkt interne Datenstrukturen für jeden Typ zu, auf den der Main-Code verweist.

In unserem Fall haben wir nur einen Typ Konsole, daher ordnet CLR über diese interne Struktur eine interne Datenstruktur zu, und wir verwalten den Zugriff auf die referenzierten Typen

Innerhalb dieser Datenstruktur enthält CLR Einträge zu allen von diesem Typ definierten Methoden. Jeder Eintrag enthält die Adresse, an der sich die Implementierung der Methode befindet.

Bei der Initialisierung dieser Struktur setzt CLR jeden Eintrag in undokumentierte FUNCTION, die in CLR selbst enthalten ist. Und wie Sie sich vorstellen können, wird diese FUNCTION als JIT-Compiler bezeichnet.

Insgesamt können Sie JIT Compiler als CLR-Funktion betrachten, die IL in native CPU-Anweisungen kompiliert. Lassen Sie mich Ihnen im Detail zeigen, wie dieser Prozess in unserem Beispiel ablaufen wird.

1.Wenn Main's WriteLine zum ersten Mal aufruft, wird die JITCompiler-Funktion aufgerufen.

2. Die JIT-Compilerfunktion weiß, welche Methode aufgerufen wird und welcher Typ diese Methode definiert.

3.Dann durchsucht der Jit Compiler die Assembly, in der dieser Typ definiert wurde, und ruft den IL-Code für die von diesem Typ definierte Methode ab, in unserem Fall den IL-Code der WriteLine-Methode.

4.JIT-Compiler ordnen DYNAMIC- Speicherblock zu, danach überprüft und kompiliert JIT IL-Code in nativen CPU-Code und speichert diesen CPU-Code in diesem Speicherblock.

5.Dann kehrt der JIT-Compiler zum internen Datenstruktureintrag zurück und ersetzt die Adresse (die sich hauptsächlich auf die IL-Code-Implementierung von WriteLine bezieht) durch einen neuen dynamisch erstellten Speicherblock, der native CPU-Anweisungen von WriteLine enthält.

6. Schließlich springt die JIT-Compiler-Funktion zum Code im Speicherblock. Dieser Code ist die Implementierung der WriteLine-Methode.

7.Nach der Implementierung der WriteLine kehrt der Code zum Hauptcode zurück, der die Ausführung wie gewohnt fortsetzt.

So_oP
quelle
0

.NET Framework verwendet die CLR-Umgebung, um die MSIL (Microsoft Intermediate Language) zu erstellen, die auch als IL bezeichnet wird. Der Compiler liest Ihren Quellcode und generiert beim Erstellen / Kompilieren Ihres Projekts MSIL. Wenn Sie nun Ihr Projekt endgültig ausführen, wird der .NET JIT ( Just-in-Time-Compiler ) aktiviert. JIT liest Ihren MSIL-Code und erzeugt nativen Code (x86-Anweisungen), der von der CPU einfach ausgeführt werden kann. JIT liest alle MSIL-Anweisungen und führt sie Zeile für Zeile aus.

Wenn Sie interessiert sind, was hinter den Kulissen passiert, wurde dies bereits beantwortet. Bitte folgen Sie - hier

Dikshit Kathuria
quelle