JS on Server Ft. Google V8 Engine
Node.jsを初めて学び始めたとき、ある一文が私の心を完全に奪いました。
「Node.jsはC++で作られたプログラムです」
それまで、Node.jsはただのJavaScriptで書かれたJavaScriptフレームワークだと思っていました。でも、この一文を見たとき、「え、どういうこと?C++で作られているってどういう意味?」と驚き、立ち止まらずにはいられませんでした。
そこで出会ったのが、V8 JavaScriptエンジンです。これはGoogleが開発し、Chromeブラウザ内で使われているものです。そして驚くべきことに、このV8もC++で作られているプログラムなのです。
つまり、簡単に言うと、
「JavaScriptエンジン(V8)は、JavaScriptコードを動かすためにC++を使っている」
ということです。この事実に、私は本当に驚きました。JavaScriptを書いているつもりでも、その裏側ではC++が頑張って動いているんだと気づかされた瞬間でした。
V8の公式説明には、こんな重要な一文があります:
V8はどんなC++アプリケーションにも埋め込むことができます。
Node.jsの作者は、この特性をどう活用したのでしょうか?彼はV8を埋め込んだC++プログラムとしてNode.jsを作り上げたのです。でもここで、もうひとつ興味深い疑問が浮かびます、
V8がJavaScriptコードを実行できるなら、そもそもNode.jsはなぜ必要だったのか?
その答えは、Node.jsが提供する“特別な力”にあります。それは、V8だけでは実現できないサーバーサイドAPIのような機能です。Node.jsは、まさにこの“スーパーパワー”を持つことで、私たちの開発を新しい次元へと引き上げてくれるのです。
Node.jsにできてV8にできないこと
サーバーにSQLデータベースがインストールされていると想像したらそのデータベースにJavaScriptで接続したい場合、V8だけではそれを実現することはできません。なぜなら、V8には以下見たいの機能がないからです:
- データベースへのアクセス
- ファイルの読み書き
- フォルダ内の画像の取得など
ここで登場するのがNode.jsです。Node.jsは、V8を拡張して、JavaScriptがオペレーティングシステム、データベース、ファイルなどとやり取りできるようにするAPIを提供します。
このAPIのおかげで、Node.jsはサーバーサイドアプリケーションを構築する際に非常に強力な存在となっているのです。
Node.jsの中身とは?
Node.jsのGitHubリポジトリを見てみると、興味深い事実に気づきます。それは、Node.jsのコードの大部分がJavaScriptと C++ で書かれているということです。
Node.jsは、次のように役割を分担しています:
C++: 低レベルの機能を処理し、V8エンジンを埋め込む役割を担当。
JavaScript: 私たち開発者が直接使えるAPIやモジュールを実装。
つまり、Node.jsはC++を土台にして、JavaScriptを外の世界(OSやファイルシステム、ネットワークなど)とつなぐ橋のような存在なのです。
V8エンジンにコードを渡すとどうなるか?
パース(Parsing)
コードを処理する最初のステップはパースです。これは、コードを分解し、実行準備を整えるプロセスです。パースは主に以下の2つのフェーズから成ります:
a. 字句解析(Lexical Analysis: Code → Tokens)
このフェーズでは、コードが「トークン」と呼ばれる小さな単位に分解されます。このプロセスは トークン化(Tokenization) とも呼ばれます。
b. 構文解析(Syntax Analysis: Tokens → AST)
字句解析で生成されたトークンを使って、抽象構文木(AST: Abstract Syntax Tree) が構築されます。
ASTとは?
ASTは、コードの文法構造をツリー形式で表現したものです。
例えば、let a = 10;というコードのASTは以下のようになります:
VariableDeclaration
├── kind: "let"
└── declarations
└── VariableDeclarator
├── id: Identifier (name: "a")
└── init: Literal (value: 10)
AST生成後のV8エンジンにおける実行パイプライン
1. Ignitionインタープリタ(AST → バイトコード)
抽象構文木(AST)が生成された後、Ignitionインタープリタに渡されます。このインタープリタの主な役割は、ASTをバイトコードに変換することです。
バイトコードとは、JavaScriptエンジンが効率的に実行できるよう最適化された低レベルのコード表現です。
実行開始
バイトコードが生成されると、実際のコード実行が始まります。
2. Just-In-Timeコンパイル:
JavaScriptはJust-In-Time(JIT)コンパイルを採用しており、インタープリタとコンパイラの両方を組み合わせることでパフォーマンスを向上させます。このプロセスで重要な役割を果たすのが、V8のTurboFanコンパイラです。
プロセスの流れ
a. コードの実行開始
ASTがIgnitionに渡され、コードの解釈と実行が行われます。
b. "ホットコード"の特定
実行中に、Ignitionが頻繁に実行される部分(ホットコード)を特定します。
c. ホットコードの最適化
ホットコードが検出されると、IgnitionはそのコードをTurboFanコンパイラに渡します。
TurboFanは、ホットコードを高度に最適化されたマシンコードにコンパイルします。
d. 高速な再実行
次回ホットコードが実行される際には、バイトコードではなく最適化されたマシンコードが直接使用されるため、実行速度が飛躍的に向上します。
このように、V8エンジンのパイプラインは、インタープリタとコンパイラの強みを組み合わせた高度な設計により、JavaScriptコードの効率的かつ柔軟な実行を可能にしています。
Resources
AST Explorer: https://astexplorer.net/
Google V8 Doc: https://v8.dev/
V8 Github: https://github.com/v8/v8
Node JS Github: https://github.com/nodejs/node
Discussion