高速な動的言語インタプリタの作り方
16時間前原文(zef-lang.dev)
概要
- Zefという動的言語のASTウォーク型インタプリタを最適化し、LuaやCPythonに匹敵する速度を達成
- JITやGC最適化ではなく、基礎部分の工夫で16倍以上の高速化を実現
- 値表現、インラインキャッシュ、オブジェクトモデル、ウォッチポイントなどの技術を解説
- ScriptBench1によるベンチマークで他言語と比較
- 各最適化の具体的な手法と効果を順を追って説明
Zefインタプリタ最適化の旅:基礎からLua/QuickJS/CPython並みの速度へ
- Zefは筆者が趣味で作った動的言語
- ASTウォーク型インタプリタの最適化でLuaやQuickJS、CPythonに近い速度を目指す試み
- 多くの言語実装記事はJITやGC改善が中心だが、本記事は基礎からの最適化に焦点
- 紹介する最適化はSSAやGC、バイトコード、機械語不要のシンプルなもの
- 16倍(Yolo-C++移植で67倍)の高速化を達成
評価方法とベンチマーク
- ScriptBench1という独自ベンチマークを作成
- Richards(OSスケジューラ)、DeltaBlue(制約ソルバー)、N-Body(物理シミュレーション)、Splay(二分木テスト)
- 他言語(JavaScript、Python、Lua)版も用意し比較
- Ubuntu 22.04.5, Intel Core Ultra 5 135U, 32GB RAM, Fil-C++ 0.677で測定
- Lua 5.4.7(GCC 11.4.0)、QuickJS-ng 0.14.0(公式バイナリ)、CPython 3.10(標準)を使用
- 各実験は30回ランダムに実行し平均値を利用
オリジナルZefインタプリタの特徴
- 64ビットタグ付き値(ダブル、32ビット整数、Object*)を使用
- JavaScriptCore由来のNaNタグ付けで高速パスとヒープ割り当て回避を実現
- C++ベースで低レベル最適化が可能
- **Fil-C++**を利用し、GCやメモリ安全性を自動化(ただし4倍程度遅くなる)
- ASTノードごとに仮想関数evaluateを再帰呼び出しする構造
- 変数名やキーにstd::string、スコープ管理にunordered_mapを多用
- クロージャやスコープチェーンもC++で再帰的に管理
- 設計はシンプルだが、CPythonの35倍、Luaの80倍、QuickJSの23倍遅い
主な最適化とその効果
- Zef Baseline
- 他言語に比べて大幅に遅い(例:CPythonの35倍遅い)
最適化 #1: 演算子の直接呼び出し
- パーサが各演算子ごとに**専用ASTノード(Binary<>やUnary<>)**を生成
- 文字列比較によるディスパッチを排除し、高速な関数呼び出しに
- 17.5%高速化
- まだCPythonの30倍遅いが、一歩前進
最適化 #2: RMW演算子(+=など)の直接呼び出し
- RMW(Read Modify Write)演算子も専用ノード化
- enumによる分岐でディスパッチ高速化
- 3.7%高速化
- 1.22倍速になり、CPythonの29倍遅い
最適化 #3: IntObject判定の回避
- isInt()の仮想関数呼び出しを回避し、int32/doubleのみを高速パスに
- IntObjectの処理はIntObject自身に委譲
- 1%の高速化
- 1.23倍速、CPythonの29倍遅い
最適化 #4以降: さらなる高速化と工夫
- シンボル(Symbol)化による文字列比較の削減
- 値のインライン化でヒープアクセスを減少
- オブジェクトモデル刷新とインラインキャッシュ導入で大幅な高速化
- 引数処理、ゲッター/セッター、callMethodのインライン化
- ハッシュテーブル最適化、std::optional回避、引数の特殊化
- 値のスローパス改善、冗長な評価関数の重複排除、sqrtやtoStringの高速化
- 配列リテラルの特殊化、callOperatorの最適化、C++設定の改善、assertの除去
- Yolo-C++移植でさらに大幅な高速化(66倍以上)
まとめ
- 基礎的な設計や値表現の選択がインタプリタ性能に大きく影響
- JITやGC以前にできる最適化で、成熟した言語処理系に匹敵する速度が狙える
- シンプルな工夫の積み重ねで、動的言語のインタプリタでも十分なパフォーマンスを実現可能
参考:最適化ごとの速度比較(抜粋)
| 実装・最適化 | ベース比 | CPython比 | Lua比 | QuickJS比 | |----------------------|---------|----------|---------|-----------| | Zef Baseline | 1x | 35.4x遅 | 79.6x遅 | 22.6x遅 | | Direct Operators | 1.175x | 30.2x遅 | 67.7x遅 | 19.2x遅 | | Object Model & IC | 6.818x | 5.2x遅 | 11.7x遅 | 3.3x遅 | | Array Literal Spec | 15.351x | 2.3x遅 | 5.2x遅 | 1.47x遅 | | Value callOperator | 16.344x | 2.2x遅 | 4.9x遅 | 1.38x遅 | | Yolo-C++ | 66.962x | 1.9x速 | 1.2x遅 | 3x速 |
今後の展望
- GCやJITの導入前でも、基礎的な設計と地道な最適化で十分な高速化が可能
- 動的言語インタプリタの実装経験や最適化の勘所を学びたい人に有用な事例