大きなWebAssemblyモジュールの読み込みはかつて直列的な処理だった。ブラウザはファイル全体をArrayBufferにダウンロードし、それをJavaScriptエンジンに渡し、それからようやくコンパイルを開始した。高速な接続ではネットワークがもはやボトルネックではなく、デコードとコンパイルのCPU時間がボトルネックとなっており、しかもそのすべてがダウンロード後にメインスレッドを停止させたまま行われていた。

明白な修正策にはそれぞれコストが伴った。単一の最適化コンパイラは高速なコードを生成するが起動が遅かった。JavaScriptエンジンは実行時に遅延コンパイルすることで無駄な処理を避けたが、コンパイルはメインスレッド上に残り、パースの完了を待つ必要があった。そして、ファイルが到着する前にコンパイルする方法はないように思われた。なぜなら旧APIは完全なArrayBufferしか受け付けなかったからだ。

Firefox 58は両端から攻めた。ストリーミングコンパイルはダウンロードされた各チャンクをストリーミングAPIを通じて直接エンジンに送り込み、wasmモジュールのコードセクションはデータセクションより前に来るため、数メガバイトのデータがまだ転送中でもコンパイルを開始できた。新しい段階的コンパイラは、最適化コンパイラより10〜15倍高速にコードを構築するベースライン段階を追加し、その出力は約2倍遅く実行されるだけだったため、コードはほぼ即座に実行され始めた。その後バックグラウンドスレッドがモジュール全体をオプティマイザで再コンパイルし、ホットスワップした。両方のコンパイラはコア間で関数ごとに作業を分割した。

コンパイルのスループットがネットワークのスループットを上回るようになったため、最後のパケットが届くほぼ同時にコードは準備できた。Mozillaはこの変更を、JavaScriptのパースではなく画像のデコードのようにWebAssemblyを読み込ませるものと位置づけた。メインスレッドのコンパイル停止も、ペイロード全体の待機もない。

到着するバイトを高速なベースライン段階へストリームし、そのコードを即座に実行し、バックグラウンドで最適化されたコードをホットスワップする

最初のパケットをコンパイルすることで、ダウンロードしてからコンパイルするという直列化ではなく、ダウンロード時間をCPU作業の中に隠す。

10〜15倍高速にコンパイルするベースライン段階は起動を素早い開始に変える。約2倍遅いコードは最適化が進む間に実行される。

wasmの関数は独立してコンパイルされるため、作業は1つのスレッドに待ち行列を作るのではなくコア間に分散される。

モジュールのコードをデータより前に置くことで、ペイロードのバイトがまだ到着している間に高コストな部分をストリーミングコンパイルできる。

各変更は最終的に実行されるマシンコードを変えずに起動を短縮するため、ピーク性能は損なわれない。

ボトルネックとなるステップが入力全体を待つ場合、最初のチャンクで開始し、まず安価なバージョンを出荷する。ブロックするのではなくバックグラウンドでアップグレードする

Firefox 58は2018年1月にストリーミングおよび段階的コンパイラを出荷した。発表の解説記事はHacker Newsで912ポイントのスレッドを集めた。Mozillaのテストページではユーザーが自身のデバイスでコンパイル速度を測定でき、チームはコンパイル済みマシンコードをHTTPキャッシュにキャッシュするための基盤が整ったと述べた。これは同じリリースで出荷されたJavaScriptバイトコードキャッシュを拡張するものだった。

出典をたどる

出典

  1. Making WebAssembly even faster: Firefox's new streaming and tiering compiler Mozilla Hacks · hacks.mozilla.org ·
  2. Hacker News discussion: Making WebAssembly even faster Hacker News · news.ycombinator.com ·