はじめに
エンジニアリングに関する動画を見漁っていたところ、ブラウザの「イベントループ」というキーワードを耳にして、知識欲から調べました。その後、JavaScriptエンジン(とりあえずV8で)にも興味が湧き、ChatGPTに教えてもらいながら議論したものをイメージ図として起こしたので(まるでモンタージュみたい)、記録として記事にしてみました。イベントループに関しては、またの機会にしようと思います。
どんなときに読むのがいいか
- 同期処理に関して実行されるまでの流れをざっくり理解したいとき
- JavaScriptと仲良くしたいとき
- JavaScriptエンジンの中でも、V8にちょっと興味あるとき
JavaScriptエンジン、V8とは
JavaScriptエンジンとは、Wikipediaにはこう書いてあります。
JavaScriptエンジンは、JavaScriptのコードを実行するコンピュータプログラムである。初期のJavaScriptエンジンは単なるインタプリタであったが、近年の全てのエンジンは、性能の向上のためにJITコンパイルを利用している。
JavaScriptはブラウザー上で動くプログラムなので、ブラウザーにJavaScriptエンジンが埋め込まれています。また、ブラウザーによって採用されているものが異なります:
ブラウザ | JavaScriptエンジン |
---|---|
Chrome, Edge | V8 |
Firefox | SpiderMonkey |
Safari | JavaScriptCore |
旧Edge | Chakra |
この差により、JavaScriptの実行結果・パフォーマンス・挙動は、ブラウザごとで微妙に違いが出てくることも。余談ですが、表示が異なるのはレンダリングエンジによる差の方が大きいと思われます。
また、JavaScriptを動かせる環境はブラウザに限らず Node.jsやDenoなどでもできます。
そう、Node.js、Denoに搭載されているのもV8なのです。
V8

V8 は、Google が開発したC++製のオープンソースの高性能 JavaScript / WebAssembly エンジンです。
V8と言えば V型8気筒エンジンと、車のエンジンに興味のある人ならそう連想する人がいるかもしれないです。私も車載系の組み込みエンジニアだったのでそう思いました。しかし、案外間違っていなく、
The lead developer of V8 was Lars Bak, and it was named after the powerful car engine.
V8 (JavaScript engine)
米国製自動車ではV8エンジンが高性能を表現するステータスシンボルであり、Google ChromeのV8はそのシンボルにあやかって命名されたものであるという。
「V8」エンジンに込めた高速化の願い、Google Chromeの狙いとは
とのこと。V8内のコンポーネントの名前も(Igniton, Turbofanなど)この並びの命名です。エンジニアのこういう遊び心あるような命名センスいいですね。個人的に好きです。
図解
本題です。どん。
ざっくりFLOW
JSのコード
↓
Parse、ASTに変換
↓
Ignition [ASTを中間バイトコードに変換、プロファイルしコールドパスとホットパスに分ける]
↓
Turbofan [ホットパスをバイトコードからマシンコードにコンパイル、CPUの命令列を生成しメモリに配置(このポインタを渡す)]
↓
コールスタックで実行順番待ち
↓
バイトコード:Ignition(JS VM)で実行
Turbofan経由のやつ:コールされてCPUで実行
Ignitionはバイトコードの生成から、プロファイル、実行まで担っている。
用語
- コールドパス / Cold Path
- 実行頻度が低い、またはプロファイル情報が不足していて、解釈実行(バイトコード)のまま動く経路
- ホットパス / Hot Path
- 実行頻度が高く、型が安定し、最適化コンパイラ(JIT)によりネイティブコード化(高速化)される処理経路
- バイトコード / bytecode
- VM(Virtual Machine)で動かすためにコンパイルされた中間命令列
余談
バイトコードは「言語によってまったく異なる設計」をしている
前提として、バイトコードは「VMごとの命令セット」。すなわち、言語ごとに違うのが必然。「バイトコード」ってひとくちに言うけど、共通フォーマットはない!
→ 各言語・実行環境が「自分のVMでうまく動くように」独自設計している命令体系です。
言語 | バイトコード構造 | バイトコードの特徴 | 補足 |
---|---|---|---|
JavaScript(V8 Ignition) | スタック型 | 軽量・高速解釈向けに最適化された中間命令列 | 実行時にASTから生成。Turbofanの前段階 |
Python(CPython) | スタック型 | 単純な命令セット(1バイトのオペコード+引数) |
.pyc ファイルに保存。インタプリタ用に分かりやすい設計 |
Java(JVM) | スタック型 | 命令セットが豊富で型安全 |
.class ファイルに含まれ、HotSpot JITが対象 |
WebAssembly(WASM) | バイナリ命令型 | バイトコードというより構造化されたバイナリフォーマット | 高速デコード&セキュア実行を想定。命令は32bit前提など規格がガチガチ |
WASMに関して、バイナリ命令型(.wasmなど)はネイティブコードではない。
すなわち、VM上で動くことに変わりはないが、他よりも高速に実行できる。
なぜか?
- 構文構造が明示的(デコードしやすい)
- 型付き(最適化しやすい)
- メモリ管理が制限されている(セキュリティと性能)
- 機械語に近いバイナリ設計(コンパイルが早い)
バイトコード実行のイメージ
- JavaScriptのVM(Iginition)
-
ワンマンプレイヤー:「1足す2ね、はい、3!」CPUに3を送る など
「1足す2ね、はい、3!」…と、逐一考えて都度実行
最終的に「やってほしい仕事」だけをCPUに伝える → 前後の文脈とか教えてくれない - WASMのVM
-
周りを信頼して“全体最適”を指示できる司令塔:「この式はこう処理して、そのあとこう繋がるから、流れで任せたよ」
構造・型・目的すべてが最初から明示されているから、すんなりネイティブコードにコンパイルしてCPU実行
コメント
WASM...🦀(Rust)を最近キャッチアップし始めたわけですが、これやると他の言語の見方が変わりますね。そして低レイヤー寄りの言語ということもあってできることの幅が広がって楽しいです。☺️スラスラかけるようになりたいが、まだ先だなあ・・・。
V8に関しては、GC(Orinoco)やループイベント絡みなど、掘らないといけないところがまだまだはたくさんあるので日々インプットしていきたいと思います。
学んだことをまとめて、並べていっただけなので、取り止めのない内容になった気がしますが、まあ、アウトプットすることが大事かと思うので良しとしよう(してください)。誰かの参考になれば。
参考