How-To

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

Wir freuen uns über kontroverse Diskussionen, die gerne auch mal hitzig geführt werden dürfen. Beleidigende, grob anstößige, rassistische und strafrechtlich relevante Äußerungen und Beiträge tolerieren wir nicht. Bitte achte darauf, dass du keine Texte veröffentlichst, für die du keine ausdrückliche Erlaubnis des Urhebers hast. Ebenfalls nicht erlaubt ist der Missbrauch der Webangebote unter t3n.de als Werbeplattform. Die Nennung von Produktnamen, Herstellern, Dienstleistern und Websites ist nur dann zulässig, wenn damit nicht vorrangig der Zweck der Werbung verfolgt wird. Wir behalten uns vor, Beiträge, die diese Regeln verletzen, zu löschen und Accounts zeitweilig oder auf Dauer zu sperren.

Trotz all dieser notwendigen Regeln: Diskutiere kontrovers, sage anderen deine Meinung, trage mit weiterführenden Informationen zum Wissensaustausch bei, aber bleibe dabei fair und respektiere die Meinung anderer. Wir wünschen Dir viel Spaß mit den Webangeboten von t3n und freuen uns auf spannende Beiträge.

Dein t3n-Team

Ein Kommentar
dermorzi
dermorzi

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

Antworten

Melde dich mit deinem t3n Account an oder fülle die unteren Felder aus.