Dev Study
← ブラウザの仕組み
レッスン 6 / 6·約 9 分·第5期 ブラウザの仕組み

JITと脱最適化 — 仮定して速くし、外れたら捨てる

リクエストがアプリケーションに届くまで
  1. ブラウザURL入力今ここ
  2. DNS名前→IP
  3. ルータ経路選択
  4. ロードバランサ振り分け
  5. Nginx受け口
  6. アプリ処理
  7. DB保存

第6週で、CPUが条件の確定を待たずに予測して進み、外したら捨てることを見ました。今回は同じ発想が、JavaScriptエンジンでソフトウェアとして行われる話です。

まず結論

V8は過去のフィードバックから「この変数は整数だろう」と**仮定して**機械語を作ります。仮定が破られると、作った最適化コードを**捨てて**最適化前の実行に戻ります。これを脱最適化(deoptimization)と呼びます。

「V8は2段」と説明されますが、2026年現在は Ignition → Sparkplug → Maglev → TurboFan の**4段**です。2段と断定すると誤りになります。

? 読む前に予測

ある関数を最適化した機械語にコンパイルした後、想定と違う型の引数が渡されました。エンジンは何をするでしょうか。

当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。

抽象原理

なぜ一度バイトコードにするのか

V8はソースをいきなり機械語にはしません。まずバイトコードにします。理由は3つ。ひとつ目はメモリです。

Before Ignition, the code generated by V8's Full-codegen baseline compiler typically occupied almost one third of the overall JavaScript heap in Chrome.

Chromeの JavaScript ヒープのほぼ3分の1が機械語で埋まっていたのです。バイトコードなら4分の1から半分。ふたつ目が本題です。

Ignition's bytecode provides a cleaner and less error-prone baseline execution model in V8, simplifying the deoptimization mechanism that is a key feature of V8's adaptive optimization.

ソースから作り直さずバイトコードから直接最適化できる。公式は、これが脱最適化の仕組みを単純にすると書いています。みっつ目は起動の速さ。

具体実物

仮定して速くし、外れたら捨てる

最適化コンパイラは何をしているのか。公式ブログを逐語で引きます。

Fast execution of JavaScript relies heavily on speculative optimizations. That is, JIT-compilers make assumptions when generating machine code based on feedback that was collected during earlier executions. For example, given the expression a + b, the compiler can generate machine code for an integer addition if past feedback indicates that a and b are integers (and not strings, floating point numbers, or other objects). Without making such assumptions, the compiler would have to emit generic code that handles the full behavior of the + operator in JavaScript, which is complex and thus much slower.

+ は加算にも連結にもなります。全部に対応するコードは遅い。そこで過去の実行で a も b も整数なら、整数加算の機械語を出してしまう。これが仮定です。

では仮定が外れたら。

If the program later behaves differently and thus violates assumptions made when generating the optimized code, V8 performs a deoptimization (or deopt, for short). That means throwing away the optimized code and continuing execution in unoptimized code (and collecting more feedback to possibly tier-up again later).

作った最適化コードを捨てて、最適化前の実行に戻る。後でまた上の段へ上がることもある。

投機的最適化と脱最適化(V8公式ブログの記述より)
フィードバックを集める"feedback that was collected during earlier executions"○ a も b も整数だった
仮定して機械語を作る"make assumptions when generating machine code"○ 整数加算に特化・速い
仮定どおりの値が来る整数が渡され続ける○ 速いまま
仮定が破られる"violates assumptions made when generating the optimized code"× 最適化コードを捨てて最適化前へ戻る
↓
速さは「全部に備える」ことではなく「絞って賭ける」ことから来ている

最適化コードには最適化後から最適化前への対応表が付いていて、脱最適化の側が解釈します。

✓ ここまでの確認

「V8はソースを直接、最適化された機械語にコンパイルする」はどう評価できますか。

⇄ 2つを見比べる
具体例 A

CPUの分岐予測(第6週)

第6週で見たとおり、CPUは条件の計算が終わる前に履歴から行き先を予測して進みます。外れれば進めた分を破棄して15〜20サイクルを失いますが、計算結果は正しいままです。予測は正しさではなく速度だけの問題でした。

ハードウェアが行う投機表
観測 分岐がどちらへ進んだかの履歴
仮定 「たぶんこちら」と予測して投機実行
外れたら パイプラインをフラッシュして詰め直し
粒度 命令単位・サイクル単位
具体例 B

V8の投機的最適化(本レッスン)

V8は過去のフィードバックから「この値は整数だろう」と仮定し、特化した機械語を作ります。仮定が破られると最適化コードを捨てて最適化前へ戻ります。V8自身が speculative・assumption・feedback・throwing away という語を使っています。

ソフトウェアが行う投機(V8公式の語)表
観測 "feedback ... collected during earlier executions"
仮定 "make assumptions when generating machine code"
外れたら "throwing away the optimized code"
粒度 関数単位

ハードウェアの分岐予測と、ソフトウェアのJITコンパイラ。層も作る人も違いますが、共通する構造は何でしょうか。そして、どこが違うでしょうか。

両方とも「待てば確実に分かること」を待たずに進んでいます。待たないぶん何を得て、外れたとき何を失うかを並べてみてください。

抽象原理

オブジェクトの「形」——V8の呼称は Map

仮定を置くには、オブジェクトの形が安定している必要があります。公式ドキュメントのタイトルは "Maps (Hidden Classes) in V8" です。

Map: the hidden class itself. It's the first pointer value in an object and therefore allows easy comparison to see if two objects have the same class.

先頭のポインタ値なので、同じ形かを一瞬で比べられます。形が同じなら、同じ場所に同じプロパティがあると仮定できます。

具体実物

形が崩れると、何が起きるか

形が崩れると何が起きるのか。プロパティは3種類あり、in-object と fast はメタ情報を共有します。問題は3つ目。

However, if many properties get added and deleted from an object, it can generate a lot of time and memory overhead to maintain the descriptor array and HiddenClasses. Hence, V8 also supports so-called slow properties. An object with slow properties has a self-contained dictionary as a properties store. All the properties meta information is no longer stored in the descriptor array on the HiddenClass but directly in the properties dictionary. Since inline caches don't work with dictionary properties, the latter, are typically slower than fast properties.

追加削除が多いと、自己完結した辞書に移ります。代償は太字の部分です。インラインキャッシュが効かなくなる。形を共有しているから「探し方」を覚えられたのであって、形の情報が各オブジェクトに散れば、探し方は使えません。

✓ ここまでの確認

V8の「隠しクラス」の公式な呼称と、形が崩れたときに起きることの組み合わせはどれですか。

この章のまとめ

  • V8はソースを直接機械語にせず、まずバイトコードにする。理由はメモリ削減・バイトコードから直接最適化できること・起動が速いことの3つ。
  • 「V8は2段」は今は段数が足りない。 2026年現在は Ignition → Sparkplug → Maglev → TurboFan の4段。古い記事は書かれた時点では正しかった。
  • 最適化は投機的。仮定が破られると脱最適化し、"throwing away the optimized code" して最適化前へ戻る。
  • 第6週の分岐予測と構造は同型と言ってよい(V8自身が同じ語彙を使う)。ただし粒度は関数単位、担い手はソフトウェア。コストの数値は一次ソースに無く、定量比較はできない。
  • オブジェクトの形は V8では Map(Shape は SpiderMonkey の呼称)。追加削除が多いと辞書に落ち、インラインキャッシュが効かなくなる。
  • ICの動作を説明した v8.dev の公式ページには到達できなかった。 説明は V8開発者の個人サイトに頼るしかなく、引くなら公式ではないと明示する。

自分の現場で確かめる

読んだだけでは身につきません。手元の環境で実際に見てみてください。

理解度チェック

読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。

問題 1 / 4正解: 0

「V8は Ignition と TurboFan の2段構成」を2026年時点でどう評価すべきですか。

用語集(6語)
Ignition
V8のインタプリタ。JavaScriptをバイトコードにして実行する。レジスタマシンと呼ばれるが、そのレジスタはCPUのものではない。
投機的最適化 (speculative optimization)
過去のフィードバックに基づき「この値は整数だろう」と仮定を置き、特化した機械語を生成すること。
脱最適化 (deoptimization / deopt)
仮定が破られたとき、最適化コードを捨てて最適化前の実行に戻ること。
Map(隠しクラス)
V8がオブジェクトの「形」を表す内部構造。**公式呼称は Map**。先頭のポインタ値なので同じ形かを高速に比較できる。
slow properties(辞書モード)
追加削除が多いオブジェクトで使う辞書型の格納。インラインキャッシュが効かないため遅い。
インラインキャッシュ (IC)
プロパティの「探し方」を命令に埋め込んで覚える仕組み。出典は V8開発者の個人サイトで、公式ではない。