t3n 53

JavaScript-Engines: Die Turbolader für den Browser

(Shutterstock / Art Alex)

Moderne Webbrowser setzen auf moderne Javascript-Engines, um eine schnelle ­Ausführung von Code zu gewährleisten – Entwickler sollten also ein ­grundlegendes ­Verständnis für ihre Funktionsweise mitbringen. Eine Übersicht der ­gängigsten ­Javascript-Engines.

Der Grundauftrag aller Javascript-Engines liegt in der Konvertierung von Javascript-Code in schnellen, optimierten Code, den Browser und Webanwendungen dann interpretieren können. ­Dabei setzt jeder Browser auf eine eigene, spezifische Engine, etwa V8 in Google ­Chrome, Chakra in Microsoft Edge oder Spidermonkey in Mozilla ­Firefox.

Die Javascript-Engine-Pipeline

Für sie alle gilt: Ihr Einsatz beginnt mit dem Javascript-Quellcode, den der Entwickler geschrieben hat. Die Javascript-Engine analysiert ihn und wandelt ihn in einen Abstract Syntax Tree (AST) um – eine Baumdarstellung des Quellcodes, welcher ­anschließend in Bytecode umgewandelt wird. Dieser Bytecode wird dann vom Bytecode-Interpreter ausgeführt.

Um eine bessere Ausführungsgeschwindigkeit zu erreichen, kann dieser Bytecode zusammen mit gesammelten Pro­filing-Daten an einen optimierenden Compiler geschickt werden. Der optimierende Compiler trifft aufgrund dieser Profiling-Daten bestimmte Annahmen und erzeugt dann hochoptimierten Maschinencode. Wenn sich eine der Annahmen irgendwann als falsch herausstellt, wird der Code deoptimiert und die Ausführung kehrt zum Interpreter zurück, der dann neue Profiling-Daten sammelt, um den Code später erneut zu optimieren.

Sehen wir uns nun die Teile dieser Pipeline an, die tatsächlich für die Ausführung von Javascript-Code zuständig sind. Das heißt: Wo genau wird Code interpretiert und optimiert? Wir konzentrieren uns dabei auf einige der Unterschiede zwischen den populärsten Javascript-Engines. Üblicherweise besteht die ­Pipeline immer aus einem Interpreter und zumindest einem ­optimierenden Compiler. Der Interpreter erzeugt sehr schnell ­Bytecode, der optimierende Compiler benötigt etwas länger, erzeugt aber dafür hochoptimierten Maschinencode.

Dieser generische Ansatz entspricht ziemlich genau dem Ansatz von V8, der Javascript-Engine in Chrome und Node.js. Der Interpreter in V8 heißt Ignition, der optimierende Compiler heißt Turbofan. Eine weitere populäre Javascript-Engine ist Spidermonkey, schon in den 1990er Jahren von Brendan Eich entwickelt und damit die erste Javascript-Engine überhaupt. Spidermonkey kommt in Mozilla ­Firefox zum Einsatz. Die Pipeline sieht anders aus als bei V8: Hier gibt es nicht einen, sondern zwei optimierende Compiler. Auf den Interpreter folgt der Baseline-­Compiler, der einfachen Maschinencode erzeugt. In Kombination mit ­Profiling-Daten, die während der Ausführung des Codes gesammelt werden, kann der Ionmonkey-Compiler dann hochoptimierten Code erstellen. Sollten sich dabei getroffene Annahmen als falsch herausstellen, kehrt die Ausführung zum ­Baseline-Code zurück. Chakra, die Javascript-Engine in Microsoft Edge und Node-­Chakracore, hat eine sehr ähnliche Architektur, mit einem Interpreter und ebenfalls zwei Compilern, SimpleJIT und FullJIT. Dabei steht JIT für Just-in-Time-Compiler. Javascriptcore (oder kurz JSC), Apples ­Javascript-Engine, wie sie in Safari und React Native verwendet wird, bringt es mit drei verschiedenen optimierenden Compilern auf die Spitze. LLInt, der Low-Level-Inter­preter, führt zum Baseline-Compiler, der zum DFG-Compiler weiterführt. Code, der sehr häufig verwendet wird, kann dann zuletzt noch durch den FTL-Compiler optimiert werden.

Compiler helfen Javascript-Engines dabei, eine bessere Ausführungs­geschwindigkeit zu erreichen: Sie erzeugen hochoptimierten ­Maschinencode. Verschiedene ­Engines setzen auf einen, zwei oder noch mehr Compiler. (Grafik: Mathias Bynens, Benedikt Meurer)

Warum haben manche Engines mehr Compiler als andere? Ein Interpreter kann Bytecode schnell erzeugen, aber Bytecode ist generell nicht sehr effizient. Ein optimierender Compiler benötigt dagegen länger, erzeugt aber letztendlich viel effizienteren Maschinencode. Es gilt also, den richtigen Kompromiss zwischen schnellem Erzeugen von Code (Interpreter) oder schnellerer Ausführung des erzeugten Codes (Optimierender Compiler) zu finden. Einige Engines entscheiden sich dafür, mehrere optimierende Compiler mit unterschiedlichen Zeit- und Effizienzeigenschaften hinzuzufügen, was eine feinere Kontrolle über diese Kompromisse auf Kosten zusätzlicher Komplexität und Compile-Zeiten ­ermöglicht.

Bitte beachte unsere Community-Richtlinien

Eine Reaktion
dermorzi

Endlich mal wieder ein Artikel, der es werden ist ihn zu lesen. Bitte mehr davon!

Du musst angemeldet sein, um einen Kommentar schreiben zu können.

Jetzt anmelden

Hey du! Schön, dass du hier bist. 😊

Bitte schalte deinen Adblocker für t3n.de aus, um diesen Artikel zu lesen.

Wir sind ein unabhängiger Publisher mit einem Team bestehend aus 65 fantastischen Menschen, aber ohne riesigen Konzern im Rücken. Banner und ähnliche Werbemittel sind für unsere Finanzierung sehr wichtig.

Danke für deine Unterstützung.

Digitales High Five,
Stephan Dörner (Chefredakteur t3n.de) & das gesamte t3n-Team

Anleitung zur Deaktivierung