Beachten Sie, dass im Folgenden nur der Unterschied zwischen nativer und JIT-Kompilierung verglichen wird und die Besonderheiten einer bestimmten Sprache oder eines bestimmten Frameworks nicht behandelt werden. Es kann legitime Gründe dafür geben, eine bestimmte Plattform darüber hinaus zu wählen.
Wenn wir behaupten, dass nativer Code schneller ist, sprechen wir über den typischen Anwendungsfall von nativ kompiliertem Code im Vergleich zu JIT-kompiliertem Code, bei dem die typische Verwendung einer JIT-kompilierten Anwendung vom Benutzer ausgeführt werden soll, mit sofortigen Ergebnissen (z. B. Nr warte zuerst auf den Compiler). In diesem Fall glaube ich nicht, dass irgendjemand behaupten kann, dass mit JIT kompilierter Code mit nativem Code übereinstimmen oder ihn übertreffen kann.
Nehmen wir an, wir haben ein Programm in einer Sprache X geschrieben und können es mit einem nativen Compiler und erneut mit einem JIT-Compiler kompilieren. Jeder Arbeitsablauf hat die gleichen Phasen, die verallgemeinert werden können als (Code -> Zwischendarstellung -> Maschinencode -> Ausführung). Der große Unterschied zwischen zwei ist, welche Stufen vom Benutzer und welche vom Programmierer gesehen werden. Bei der nativen Kompilierung sieht der Programmierer bis auf die Ausführungsstufe alles, aber bei der JIT-Lösung sieht der Benutzer neben der Ausführung auch die Kompilierung des Maschinencodes.
Die Behauptung, dass A schneller als B ist, bezieht sich auf die Zeit, die das Programm benötigt, um ausgeführt zu werden, wie es vom Benutzer gesehen wird . Wenn wir davon ausgehen, dass beide Codeteile in der Ausführungsphase identisch ausgeführt werden, müssen wir davon ausgehen, dass der JIT-Workflow für den Benutzer langsamer ist, da er auch den Zeitpunkt T der Kompilierung zum Maschinencode sehen muss, bei dem T> 0 ist Damit der JIT-Arbeitsablauf den nativen Arbeitsablauf für den Benutzer ausführen kann, muss die Ausführungszeit des Codes verringert werden, sodass die Ausführung + Kompilierung zum Maschinencode niedriger ist als nur die Ausführungsphase des nativen Arbeitsablaufs. Dies bedeutet, dass wir den Code in der JIT-Kompilierung besser optimieren müssen als in der nativen Kompilierung.
Dies ist jedoch ziemlich undurchführbar, da zur Durchführung der notwendigen Optimierungen zur Beschleunigung der Ausführung mehr Zeit für die Kompilierung des Maschinencodes aufgewendet werden muss und somit jede Zeit, die wir durch den optimierten Code einsparen, verloren geht Wir fügen es der Zusammenstellung hinzu. Mit anderen Worten, die "Langsamkeit" einer JIT-basierten Lösung beruht nicht nur auf der zusätzlichen Zeit für die JIT-Kompilierung, sondern der durch diese Kompilierung erzeugte Code ist langsamer als eine native Lösung.
Ich werde ein Beispiel verwenden: Registerzuordnung. Da der Speicherzugriff einige tausend Mal langsamer ist als der Registerzugriff, möchten wir nach Möglichkeit Register verwenden und haben so wenig Speicherzugriff wie möglich, aber wir haben eine begrenzte Anzahl von Registern, und wir müssen den Status in den Speicher verschieben, wenn wir ihn benötigen ein Register. Wenn wir einen Registerzuweisungsalgorithmus verwenden, dessen Berechnung 200 ms dauert, und dadurch 2 ms Ausführungszeit einsparen, wird die Zeit für einen JIT-Compiler nicht optimal genutzt. Lösungen wie Chaitins Algorithmus, mit dem sich hochoptimierter Code erzeugen lässt, sind ungeeignet.
Die Rolle des JIT-Compilers besteht darin, die beste Balance zwischen Kompilierungszeit und Qualität des produzierten Codes zu finden, wobei jedoch die schnelle Kompilierungszeit eine große Rolle spielt, da Sie den Benutzer nicht warten lassen möchten. Die Leistung des ausgeführten Codes ist im JIT-Fall langsamer, da der native Compiler bei der Optimierung des Codes nicht an die Zeit gebunden ist und daher die besten Algorithmen verwenden kann. Die Möglichkeit, dass die Gesamtkompilierung + Ausführung für einen JIT-Compiler nur die Ausführungszeit für nativ kompilierten Code überschreiten kann, ist effektiv 0.
Unsere VMs beschränken sich jedoch nicht nur auf die JIT-Kompilierung. Sie verwenden zeitnahe Kompilierungstechniken, Caching, Hot Swapping und adaptive Optimierungen. Ändern wir also unsere Behauptung, dass die Leistung dem Benutzer entspricht, und beschränken Sie sie auf die Zeit, die für die Ausführung des Programms benötigt wird (vorausgesetzt, wir haben AOT kompiliert). Wir können den ausführenden Code effektiv dem nativen Compiler (oder besser?) Gleichsetzen. Ein großer Vorteil für VMs ist, dass sie möglicherweise Code mit einer besseren Qualität als ein nativer Compiler produzieren können, da sie Zugriff auf mehr Informationen haben - die des laufenden Prozesses, beispielsweise wie oft eine bestimmte Funktion ausgeführt werden kann. Die VM kann dann über Hot-Swapping adaptive Optimierungen auf den wichtigsten Code anwenden.
Es gibt jedoch ein Problem mit diesem Argument - es wird davon ausgegangen, dass profilgesteuerte Optimierung und dergleichen nur für VMs gilt, was jedoch nicht zutrifft. Wir können es auch auf die native Kompilierung anwenden - indem wir unsere Anwendung mit aktivierter Profilerstellung kompilieren, die Informationen aufzeichnen und dann die Anwendung mit diesem Profil neu kompilieren. Es ist wahrscheinlich auch erwähnenswert, dass Code-Hot-Swapping nicht nur von einem JIT-Compiler ausgeführt werden kann, sondern auch von systemeigenem Code - obwohl die JIT-basierten Lösungen dafür leichter verfügbar sind und den Entwickler erheblich entlasten. Die große Frage ist also: Kann uns eine VM einige Informationen bieten, die die native Kompilierung nicht kann, was die Leistung unseres Codes steigern kann?
Ich kann es selbst nicht sehen. Wir können die meisten Techniken einer typischen VM auch auf systemeigenen Code anwenden - obwohl der Prozess aufwändiger ist. Ebenso können wir Optimierungen eines nativen Compilers auf eine VM zurück anwenden, die AOT-Kompilierung oder adaptive Optimierungen verwendet. Die Realität ist, dass der Unterschied zwischen nativ ausgeführtem Code und dem, der in einer VM ausgeführt wird, nicht so groß ist, wie wir angenommen haben. Sie führen letztendlich zum gleichen Ergebnis, verfolgen jedoch einen anderen Ansatz, um dorthin zu gelangen. Die VM verwendet einen iterativen Ansatz, um optimierten Code zu erstellen, den der native Compiler von Anfang an erwartet (und der durch einen iterativen Ansatz verbessert werden kann).
Ein C ++ - Programmierer argumentiert möglicherweise, dass er die Optimierungen von Anfang an benötigt, und sollte nicht darauf warten, dass eine VM herausfindet, wie sie dies tun soll, wenn überhaupt. Dies ist bei unserer aktuellen Technologie wahrscheinlich ein gültiger Punkt, da der aktuelle Grad an Optimierungen in unseren VMs unter dem liegt, den native Compiler bieten können. Dies ist jedoch möglicherweise nicht immer der Fall, wenn sich die AOT-Lösungen in unseren VMs verbessern usw.