🔮

JS on Server Ft. Google V8 Engine

2024/12/24に公開

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


https://x-bit.co.jp/recruit/
https://herp.careers/v1/xbit
https://note.com/xbit_recruit

クロスビットテックブログ

Discussion