Enthält die Kompilierung, die einen Zwischenbytecode erzeugt (wie bei Java), im Allgemeinen weniger Komplexität (und nimmt daher wahrscheinlich weniger Zeit in Anspruch), anstatt den gesamten Weg zum Maschinencode zurückzulegen?
quelle
Enthält die Kompilierung, die einen Zwischenbytecode erzeugt (wie bei Java), im Allgemeinen weniger Komplexität (und nimmt daher wahrscheinlich weniger Zeit in Anspruch), anstatt den gesamten Weg zum Maschinencode zurückzulegen?
Ja, das Kompilieren in Java-Bytecode ist einfacher als das Kompilieren in Maschinencode. Dies liegt zum einen daran, dass es nur ein Format für das Targeting gibt (wie Mandrill erwähnt, obwohl dies nur die Komplexität des Compilers und nicht die Kompilierungszeit verringert), zum anderen daran, dass die JVM eine viel einfachere Maschine ist und bequemer zu programmieren ist als echte CPUs - wie sie entwickelt wurden Zusammen mit der Java-Sprache lassen sich die meisten Java-Operationen auf sehr einfache Weise auf genau eine Bytecode-Operation abbilden. Ein weiterer sehr wichtiger Grund ist, dass praktisch keineOptimierung erfolgt. Fast alle Effizienzprobleme bleiben dem JIT-Compiler (oder der gesamten JVM) überlassen, sodass das gesamte mittlere Ende der normalen Compiler verschwindet. Grundsätzlich kann es den AST einmal durchlaufen und fertige Bytecode-Sequenzen für jeden Knoten erzeugen. Das Generieren von Methodentabellen, Konstantenpools usw. ist mit einem gewissen Verwaltungsaufwand verbunden, der jedoch nichts mit der Komplexität von beispielsweise LLVM zu tun hat.
Ein Compiler ist einfach ein Programm, das lesbare 1 Textdateien aufnimmt und sie in binäre Anweisungen für eine Maschine übersetzt. Wenn Sie einen Schritt zurücktreten und Ihre Frage aus dieser theoretischen Perspektive betrachten, ist die Komplexität ungefähr gleich. Auf einer praktischeren Ebene sind Bytecode-Compiler jedoch einfacher.
Welche umfassenden Schritte müssen zur Erstellung eines Programms unternommen werden?
Es gibt nur zwei echte Unterschiede zwischen den beiden.
Im Allgemeinen erfordert ein Programm mit mehreren Kompilierungseinheiten eine Verknüpfung beim Kompilieren mit Maschinencode und im Allgemeinen nicht mit Bytecode. Man kann sich die Frage stellen, ob das Verknüpfen Teil des Kompilierens im Zusammenhang mit dieser Frage ist. In diesem Fall wäre die Bytecode-Kompilierung etwas einfacher. Die Komplexität der Verknüpfung wird jedoch zur Laufzeit ausgeglichen, wenn viele Verknüpfungsprobleme von der VM behandelt werden (siehe meinen Hinweis unten).
Bytecode-Compiler optimieren in der Regel nicht so stark, da die VM dies im laufenden Betrieb besser kann (JIT-Compiler sind heutzutage eine ziemlich standardmäßige Ergänzung für VMs).
Daraus schließe ich, dass Bytecode-Compiler die Komplexität der meisten Optimierungen und das gesamte Verknüpfen weglassen können, wodurch beide auf die VM-Laufzeit verschoben werden. Bytecode-Compiler sind in der Praxis einfacher, weil sie der VM viele Komplexitäten aufbürden, die Maschinencode-Compiler selbst übernehmen.
1 Ohne esoterische Sprachen
quelle
Ich würde sagen, das vereinfacht das Compiler-Design, da die Kompilierung immer von Java zu generischem Code für virtuelle Maschinen erfolgt. Das bedeutet auch, dass Sie den Code nur einmal kompilieren müssen und er auf jeder Plattform ausgeführt wird (anstatt auf jedem Computer kompilieren zu müssen). Ich bin mir nicht sicher, ob die Kompilierungszeit kürzer sein wird, da Sie die virtuelle Maschine genau wie eine standardisierte Maschine betrachten können.
Auf der anderen Seite muss auf jeder Maschine die Java Virtual Machine geladen sein, damit sie den "Byte-Code" (der aus der Java-Code-Kompilierung resultierende Code der virtuellen Maschine) interpretieren, in den tatsächlichen Maschinencode übersetzen und ausführen kann .
Imo das ist gut für sehr große Programme, aber sehr schlecht für kleine (weil die virtuelle Maschine eine Verschwendung von Speicher ist).
quelle
Die Komplexität der Kompilierung hängt weitgehend von der semantischen Lücke zwischen der Quell- und der Zielsprache und dem Grad der Optimierung ab, den Sie anwenden möchten, um diese Lücke zu schließen.
Zum Beispiel ist das Kompilieren von Java-Quellcode in JVM-Bytecode relativ einfach, da es eine Kernuntermenge von Java gibt, die so ziemlich direkt einer Untermenge von JVM-Bytecode zugeordnet ist. Es gibt einige Unterschiede: Java hat Schleifen, aber keine
GOTO
, die JVM hatGOTO
aber keine Schleifen, Java hat Generika, die JVM nicht, aber diese können leicht behandelt werden (die Umwandlung von Schleifen in bedingte Sprünge ist trivial, Löschung etwas weniger so, aber immer noch überschaubar). Es gibt andere Unterschiede, aber weniger gravierend.Das Kompilieren von Ruby-Quellcode in JVM-Bytecode ist sehr viel komplizierter (insbesondere bevor
invokedynamic
undMethodHandles
wurden in Java 7 oder genauer in der 3. Edition der JVM-Spezifikation eingeführt). In Ruby können Methoden zur Laufzeit ersetzt werden. In der JVM ist die kleinste Codeeinheit, die zur Laufzeit ersetzt werden kann, eine Klasse. Ruby-Methoden müssen daher nicht zu JVM-Methoden, sondern zu JVM-Klassen kompiliert werden. Der Ruby-Methodenversand stimmt nicht mit dem JVM-Methodenversand überein, und zuvorinvokedynamic
konnte kein eigener Methodenversandmechanismus in die JVM eingefügt werden. Ruby hat Fortsetzungen und Koroutinen, aber der JVM fehlen die Möglichkeiten, diese umzusetzen. (Die JVM'sGOTO
ist auf Sprungziele innerhalb der Methode beschränkt.) Das einzige Kontrollflussprimitiv der JVM, das leistungsfähig genug wäre, um Fortsetzungen zu implementieren, sind Ausnahmen und Coroutinen-Threads, die beide extrem schwer sind, während der gesamte Zweck von Coroutinen darin besteht, Ausnahmen zu implementieren sehr leicht sein.OTOH, das Kompilieren von Ruby-Quellcode in Rubinius-Bytecode oder YARV-Bytecode ist ebenfalls trivial, da beide explizit als Kompilierungsziel für Ruby entwickelt wurden (obwohl Rubinius auch für andere Sprachen wie CoffeeScript und vor allem für Fancy verwendet wurde). .
Ebenso ist das Kompilieren von nativem x86-Code in JVM-Bytecode nicht einfach, da es wiederum eine ziemlich große semantische Lücke gibt.
Haskell ist ein weiteres gutes Beispiel: Mit Haskell gibt es mehrere produktionsfertige Hochleistungscompiler, die nativen x86-Maschinencode produzieren. Bislang gibt es jedoch weder für die JVM noch für die CLI einen funktionierenden Compiler, da die Semantik Die Lücke ist so groß, dass es sehr komplex ist, sie zu überbrücken. In diesem Beispiel ist die Kompilierung in nativen Maschinencode weniger komplex als die Kompilierung in JVM- oder CIL-Bytecode. Dies liegt daran, dass nativer Maschinencode viel niedrigere Primitive (
GOTO
, Zeiger, ...) hat, die einfacher "gezwungen" werden können, das zu tun, was Sie wollen, als Primitive höherer Ebenen wie Methodenaufrufe oder Ausnahmen zu verwenden.Man könnte also sagen, je höher die Zielsprache ist, desto genauer muss sie mit der Semantik der Quellsprache übereinstimmen, um die Komplexität des Compilers zu verringern.
quelle
In der Praxis sind die meisten JVMs heutzutage sehr komplexe Software, die eine JIT-Kompilierung durchführt (daher wird der Bytecode von der JVM dynamisch in Maschinencode übersetzt).
Während die Kompilierung von Java-Quellcode (oder Clojure-Quellcode) zu JVM-Bytecode in der Tat einfacher ist, führt die JVM selbst eine komplexe Übersetzung in Maschinencode durch.
Die Tatsache, dass diese JIT-Übersetzung in der JVM dynamisch ist, ermöglicht es der JVM, sich auf die relevantesten Teile des Bytecodes zu konzentrieren. In der Praxis optimieren die meisten JVM mehr die heißesten Teile (z. B. die am häufigsten aufgerufenen Methoden oder die am häufigsten ausgeführten Basisblöcke) des JVM-Bytecodes.
Ich bin mir nicht sicher, ob die kombinierte Komplexität von JVM + Java zum Bytecode-Compiler wesentlich geringer ist als die Komplexität von vorzeitigen Compilern.
Beachten Sie auch, dass die meisten herkömmlichen Compiler (wie GCC oder Clang / LLVM ) den eingegebenen C-Quellcode (oder C ++ oder Ada, ...) in eine interne Darstellung umwandeln ( Gimple für GCC, LLVM für Clang), die ziemlich ähnlich ist etwas Bytecode. Dann transformieren sie diese internen Repräsentationen (sie optimieren sie zuerst in sich selbst, dh die meisten GCC-Optimierungsläufe nehmen Gimple als Eingabe und erzeugen Gimple als Ausgabe; später geben sie Assembler- oder Maschinencode daraus aus) in Objektcode.
Übrigens, mit der neuesten GCC- (insbesondere libgccjit ) und LLVM-Infrastruktur können Sie sie verwenden, um eine andere (oder Ihre eigene) Sprache in ihre internen Gimple- oder LLVM-Darstellungen zu kompilieren, und dann von den zahlreichen Optimierungsmöglichkeiten des Middle-End und Back-End profitieren. Endteile dieser Compiler.
quelle