Wie unterscheidet sich ein JIT-Compiler von einem gewöhnlichen Compiler?

22

Bei JIT-Compilern für Sprachen wie Java, Ruby und Python herrschte großer Hype. Inwiefern unterscheiden sich JIT-Compiler von C / C ++ - Compilern, und warum heißen die für Java, Ruby oder Python geschriebenen Compiler JIT-Compiler, während C / C ++ - Compiler nur Compiler genannt werden?

Ken Li
quelle

Antworten:

17

JIT-Compiler kompilieren den Code im laufenden Betrieb, unmittelbar vor ihrer Ausführung oder sogar, wenn sie bereits ausgeführt werden. Auf diese Weise kann die VM, auf der der Code ausgeführt wird, nach Mustern in der Codeausführung suchen, um Optimierungen zu ermöglichen, die nur mit Laufzeitinformationen möglich wären. Wenn die VM feststellt, dass die kompilierte Version aus irgendeinem Grund nicht gut genug ist (z. B. zu viele Cache-Ausfälle oder Code, der häufig eine bestimmte Ausnahme auslöst), wird sie möglicherweise auf andere Weise neu kompiliert, was zu einer wesentlich intelligenteren Version führt Zusammenstellung.

Auf der anderen Seite sind C- und C ++ - Compiler traditionell keine JIT-Compiler. Sie werden nur einmal auf dem Computer des Entwicklers in einem einzigen Schritt kompiliert und anschließend wird eine ausführbare Datei erstellt.

Victor Stafusa
quelle
Gibt es JIT-Compiler, die Cache-Fehler nachverfolgen und ihre Kompilierungsstrategie entsprechend anpassen? @ Victor
Martin Berger
@MartinBerger Ich weiß nicht, ob ein verfügbarer JIT-Compiler das tut, aber warum nicht? Da Computer leistungsfähiger sind und sich die Informatik weiterentwickelt, können die Benutzer das Programm weiter optimieren. Zum Beispiel, als Java geboren wurde, ist es sehr langsam, vielleicht 20-mal im Vergleich zu den kompilierten, aber jetzt ist die Leistung nicht so stark im Rückstand und kann mit einigen Compilern vergleichbar sein
phuclv
Ich habe das schon vor langer Zeit auf einem Blog-Post gehört. Der Compiler kann je nach aktueller CPU unterschiedlich kompiliert werden. Beispielsweise kann es einige Codes automatisch vektorisieren, wenn festgestellt wird, dass die CPU SSE / AVX unterstützt. Wenn neuere SIMD-Erweiterungen verfügbar sind, werden diese möglicherweise auf die neueren kompiliert, sodass der Programmierer keine Änderungen vornehmen muss. Oder ob es die Operationen / Daten arrangieren kann, um den größeren Cache zu nutzen oder um Cache-
Fehler
@ LưuVĩnhPhúc Chào! Ich bin froh, das zu glauben, aber der Unterschied besteht darin, dass z. B. der CPU-Typ oder die Cachegröße statisch sind und sich während der Berechnung nicht ändern. Dies kann problemlos auch von einem statischen Compiler durchgeführt werden. Cache Misses OTOH sind sehr dynamisch.
Martin Berger
12

JIT ist die Abkürzung für Just-in-Time-Compiler, und der Name ist misson: Zur Laufzeit werden sinnvolle Codeoptimierungen ermittelt und angewendet. Es ersetzt nicht die üblichen Compiler, sondern ist Teil von Dolmetschern. Beachten Sie, dass Sprachen wie Java, die Zwischencode verwenden, über beides verfügen : einen normalen Compiler für die Übersetzung von Quellcode in Zwischencode und eine im Interpreter enthaltene JIT für Leistungssteigerungen.

Code-Optimierungen können zwar von "klassischen" Compilern durchgeführt werden, beachten Sie jedoch den Hauptunterschied: JIT-Compiler haben zur Laufzeit Zugriff auf Daten . Das ist ein riesiger Vorteil; es richtig auszunutzen kann natürlich schwierig sein.

Betrachten Sie beispielsweise folgenden Code:

m(a : String, b : String, k : Int) {
  val c : Int;
  switch (k) {
    case 0 : { c = 7; break; }
    ...
    case 17 : { c = complicatedMethod(k, a+b); break; }
  }

  return a.length + b.length - c + 2*k;
}

Ein normaler Compiler kann nicht zu viel dagegen tun. Ein JIT-Compiler kann jedoch feststellen, dass mdas immer nur mit k==0einem bestimmten Grund aufgerufen wird (so etwas kann passieren, wenn sich der Code im Laufe der Zeit ändert). Es kann dann eine kleinere Version des Codes erstellen (und in systemeigenen Code kompilieren, obwohl ich dies konzeptionell für einen kleinen Punkt halte):

m(a : String, b : String) {
  return a.length + b.length - 7;
}

Zu diesem Zeitpunkt wird es wahrscheinlich sogar den Methodenaufruf einbinden, da er jetzt trivial ist.

Anscheinend hat die Sonne die meisten Optimierungen javacin Java 6 verworfen . Mir wurde gesagt, dass diese Optimierungen es JIT schwer machten, viel zu tun, und naiv kompilierter Code am Ende schneller lief. Stelle dir das vor.

Raphael
quelle
Übrigens ist JIT bei Vorhandensein von Typlöschungen (z. B. Generika in Java) tatsächlich für Typen von Nachteil.
Raphael
Die Laufzeit ist daher komplizierter als bei einer kompilierten Sprache, da die Laufzeitumgebung Daten sammeln muss, um eine Optimierung durchzuführen.
Saadtaame
2
In vielen Fällen wäre eine JIT nicht in der Lage sein , zu ersetzen , mmit einer Version , die nicht zu überprüfen , kda es nicht in der Lage wäre , zu beweisen , dass mwürde nie mit einer von Null bezeichnet werden k, aber auch ohne in der Lage zu beweisen , dass sie ersetzen könnte es mit static miss_count; if (k==0) return a.length+b.length-7; else if (miss_count++ < 16) { ... unoptimized code for m ...} else { ... consider other optimizations...}.
Supercat
1
Ich bin mit @supercat einverstanden und denke, Ihr Beispiel ist tatsächlich ziemlich irreführend. Nur wenn k=0 immer , das heißt, es ist keine Funktion der Eingabe oder der Umgebung, ist es sicher, den Test fallen zu lassen - aber um dies festzustellen, ist eine statische Analyse erforderlich, die sehr kostspielig und daher nur zum Zeitpunkt der Kompilierung erschwinglich ist. Eine JIT kann gewinnen, wenn ein Pfad durch einen Codeblock viel häufiger als andere verwendet wird und eine für diesen Pfad spezialisierte Version des Codes viel schneller ist. Die JIT gibt jedoch weiterhin einen Test aus, um zu überprüfen, ob der schnelle Pfad zutrifft , und nimmt den "langsamen Pfad", wenn nicht.
j_random_hacker
Ein Beispiel: Eine Funktion übernimmt einen Base* pParameter und ruft über diesen virtuelle Funktionen auf. Die Laufzeitanalyse zeigt, dass das tatsächliche Objekt, auf das immer (oder fast immer) verwiesen wird, vom Derived1Typ zu sein scheint . Die JIT könnte eine neue Version der Funktion mit statisch aufgelösten (oder sogar inline) Aufrufen von Derived1Methoden erzeugen . Diesem Code würde eine Bedingung vorangestellt, die prüft, ob pder Zeiger vtable auf die erwartete Derived1Tabelle zeigt. Ist dies nicht der Fall, wird stattdessen zur ursprünglichen Version der Funktion mit ihren langsameren dynamisch aufgelösten Methodenaufrufen gesprungen.
j_random_hacker