Moderne Websites wie Google Docs und Gmail führen so viel JavaScript aus, dass die Engine-Geschwindigkeit zwei getrennte Engpässe hatte. Der C++-Bytecode-Interpreter von Firefox war langsam und sammelte keine Typinformationen, während der Baseline-JIT jede Funktion in Maschinencode kompilierte; auf schweren Seiten bedeutete das, Tausende von Funktionen zu kompilieren, sodass Zeit in die Kompilierung statt in die Ausführung floss. Der optimierende Ion-JIT wurde erst schnell, nachdem der Baseline-JIT Typdaten gesammelt hatte.
Die naheliegenden Lösungen kosteten jeweils etwas: Den C++-Interpreter beschleunigen, und er liefert dem Optimierer weiterhin keine Typdaten; die Baseline-Kompilierung verzögern oder verschieben, und kalte Pfade oder Bailouts werden riskant; Ion-Bailouts mitten im JIT-Code fortsetzen, und jedes Feature braucht zusätzliche Metadaten und komplexen Code für Debugger und Exceptions. Mozilla fügte stattdessen eine neue Stufe hinzu, den Baseline-Interpreter, der zwischen dem C++-Interpreter und dem Baseline-JIT liegt.
Der Schritt, der dies günstig machte, war nicht, einen Interpreter von Grund auf zu schreiben. Der Baseline-JIT ist ein Template-JIT: Jede Bytecode-Anweisung bildet bereits auf eine weitgehend feste Sequenz von Maschinencode ab. Mozilla generierte dieselben Sequenzen in eine Threaded-Interpreter-Schleife, wobei ein templatisierter Codegenerator, dasselbe Frame-Layout und eine einzige JitScript-Struktur gemeinsam genutzt wurden, die die Inline-Caches und Profiling-Daten enthält, die beide Stufen verwenden.
Da die Interpretation nun Typinformationen sammelte, konnte die Aufwärmschwelle des Baseline-JIT von 10 auf 100 steigen, was die Zahl der kompilierten Funktionen stark verringerte. Firefox Nightly maß 2–8 % schnellere Seitenladezeiten, 2–10 % schnellere Devtools-Tests, Speichereinsparungen und einfacheren Code: Ion-Bailouts wurden im Interpreter fortgesetzt, und ein Großteil der komplizierten Debugger- und Exception-Behandlungsmechanik wurde entfernt.
Die günstige Stufe aus demselben Codegenerator wie die schnelle Stufe generieren, sodass Caches und Typdaten gemeinsam genutzt bleiben
Den Codegenerator des JIT wiederzuverwenden bedeutet, dass Interpreter-Verhalten und Inline-Caches konstruktionsbedingt mit dem JIT übereinstimmen, ohne eine zweite Implementierung synchron halten zu müssen.
Die Interpretation sammelt nun dieselben Typdaten, die früher der JIT sammelte, sodass die optimierende Stufe ihr Profil erhält, ohne auf die Kompilierung zu warten.
Die zehnfache Erhöhung der JIT-Aufwärmschwelle verringerte die Zahl der auf JS-lastigen Seiten kompilierten Funktionen drastisch.
Bailouts, Debugger-Unterstützung und Exception-Behandlung wurden einfacher, weil Ion im Interpreter statt mitten im JIT-Code fortgesetzt wird und weniger Metadaten benötigt.
Der Eintritt in die mittlere Stufe ist günstig: Die JitScript-Zuweisung läuft mindestens 10-mal schneller als die Baseline-JIT-Kompilierung.
Die mittlere Stufe zwischen langsam und schnell nicht von Hand schreiben: Sie aus den eigenen Vorlagen der schnellen Stufe generieren, dann bleiben Caches, Frames und Verhalten konstruktionsbedingt identisch
Der Baseline-Interpreter landete im Juli 2019 in Firefox Nightly und war für die allgemeine Veröffentlichung von Firefox 70 im darauffolgenden Oktober vorgesehen. Mozillas Messungen zeigten 2–8 % schnellere Seitenladezeiten und 2–10 % schnellere Devtools-Tests, dazu Speichereinsparungen. Die dadurch ermöglichte Bereinigung beschleunigte auch die Baseline-JIT-Kompilierung, und der erklärte nächste Schritt des Teams war, diese Kompilierung vom Hauptthread zu verlagern.
DEN BELEGEN FOLGEN
Die Quellen
- The Baseline Interpreter: a faster JS interpreter in Firefox 70 Mozilla Hacks · hacks.mozilla.org ·
- Hacker News discussion: The Baseline Interpreter: a faster JS interpreter in Firefox 70 Hacker News · news.ycombinator.com ·