解決策
Google DocsやGmailのような現代のサイトはあまりにも多くのJavaScriptを実行するため、エンジンの速度には2つの別個のボトルネックがあった。FirefoxのC++バイトコードインタープリタは低速で型情報を収集しなかった一方、Baseline JITは各関数をマシンコードにコンパイルしていた。重いページでは数千の関数をコンパイルすることになり、実行ではなくコンパイルに時間が費やされた。最適化を行うIon JITは、Baseline JITが型データを収集して初めて高速になった。
明白な修正策にはそれぞれコストが伴う。C++インタープリタを高速化しても最適化器に型データは与えられない。Baselineのコンパイルを遅延させたり移動させたりすると、コールドパスやベイルアウトが危険になる。IonのベイルアウトをJITコードの途中で再開させると、あらゆる機能に追加のメタデータと、デバッガや例外のための複雑なコードが必要になる。そこでMozillaは代わりに、C++インタープリタとBaseline JITの間に位置する新しい層、Baseline Interpreterを追加した。
これを安価にした施策は、インタープリタをゼロから書かないことだった。Baseline JITはテンプレートJITであり、各バイトコード命令はすでにほぼ固定のマシンコード列にマッピングされている。Mozillaはそれらの同じ列をスレッド化されたインタープリタループに生成し、1つのテンプレート化されたコード生成器、同じフレームレイアウト、そして両方の層が使うインラインキャッシュとプロファイリングデータを保持する1つのJitScript構造を共有した。
解釈実行が型情報を収集するようになったため、Baseline JITのウォームアップしきい値を10から100に引き上げることができ、コンパイルされる関数の数が大幅に削減された。Firefox Nightlyではページ読み込みが2〜8%高速化、devtoolsのテストが2〜10%高速化、メモリ削減、そしてコードの単純化が測定された。Ionのベイルアウトはインタープリタで再開され、複雑なデバッガおよび例外処理機構の多くが削除された。
達成したこと
高速な層と同じコード生成器から安価な層を生成し、キャッシュと型データを共有し続ける
うまくいった理由
JITのコード生成器を再利用することで、インタープリタの動作とインラインキャッシュが構造上JITと一致し、同期を保つべき第2の実装が不要になる。
解釈実行がJITが以前使っていたのと同じ型データを収集するようになったため、最適化層はコンパイルを待たずにプロファイルを得られる。
JITのウォームアップしきい値を10倍に引き上げたことで、JSを多用するページでコンパイルされる関数の数が大幅に削減された。
IonがJITコードの途中ではなくインタープリタで再開するため、必要なメタデータが減り、ベイルアウト、デバッガサポート、例外処理が単純になった。
中間層への移行は安価である。JitScriptの割り当てはBaseline JITのコンパイルより少なくとも10倍高速に実行される。
応用できること
低速と高速の間の中間層を手書きせず、高速な層自身のテンプレートから生成すれば、キャッシュ、フレーム、動作が構造上同一に保たれる
その後
Baseline Interpreterは2019年7月にFirefox Nightlyに搭載され、同年10月のFirefox 70の一般リリースに向かっていた。Mozillaの測定ではページ読み込みが2〜8%高速化、devtoolsのテストが2〜10%高速化し、さらにメモリ削減もあった。それが可能にしたクリーンアップはBaseline JITのコンパイルも高速化し、チームが示した次のステップはそのコンパイルをメインスレッドから外すことだった。
出典をたどる
出典
- 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 ·