
2026/11/12 21:51
CPython を Yk に適応させる
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
ykcpython が実証した核心的な進歩は、標準的な「ティア 1」バージョンではなく CPython の「ティア 2」インタプリタに適応することで、完全なコード互換性を維持しつつ、現実世界における顕著なパフォーマンス向上を実現できる点です。このアプローチは理論的上限よりも実践的な速度向上を優先します。AMD EPYC 7773X マシン上で AWFY スイートを実行したベンチマーク(30 プロセス実行からの平均値と 99% 信頼区間)の結果では、ykcpython はほぼすべてのシナリオにおいて標準 CPython および Python の実験的 JIT よりも高性能でありましたが、例外として DeltaBlue ベンチマークにおいては CPython よりも 2.5% スロートです。
このブレイクスルーは、CPython のティア 1 インタプリタにおける Quickening により引き起こされる頻繁な「オPCODE 遷移」に対応することで達成されており、それがトレース無効化を引き起こしていました。ティア 2 インタプリタはこの遷移をより効率的に処理し、オーバーヘッドを大幅に削減します。YkLua を用いた以前の取り組みで他の言語における概念の証明がなされたものの、CPython に適用するには、長い実行トレースの処理などの課題に対処するためには慎重な適応が必要でした。具体的には、オPCODE ロードに
yk_idempotent を使用し、_PyObject_GC_Link に対して yk_outline を用い、YkLocation ヒントを追加するなどといった最適化が重要であり、例えばこれらが Mandelbrot ベンチマークを 13.70 秒(CPython の 1.17 倍)から 5.09 秒(CPython の 0.43 倍)に削減し、標準 CPython および cpython_jit の両方をはるかに上回りました。
生産環境への導入にはまだ至っておらず、さらなる最適化作業が必要であるものの、このプロジェクトは既存のインタプリタに対する段階的な変更を通じて実質的なパフォーマンス向上を実現できることを示しています。
本文
yk ツールによる CPython JIT コンパイラの実装とベンチマーク結果
1. はじめに
今年初め、私は
yk というツールの機能についてブログ投稿を行いました。このツールは C 言語のインタプリタに自動的に JIT コンパイラを追加できるものです。
以来、より多くのインタプリタに yk を適用し、バグ修正を繰り返しながらパフォーマンスを著しく改善してきました。
- 10 月下旬: ベンチマークスイートでの測定結果を発表。
- PUC Lua インタープリタと比較して
が 2 倍高速化されていることが確認されました。yklua
- PUC Lua インタープリタと比較して
当初は成果に満足しておりましたが、**「この技術が大型のインタプリタでも機能するかどうか、どうやって判断しているのか?」**という問いかけにより、実用性の検証が必要だと気づかされました。
- PUC Lua は CPython や CRuby に比べ桁違いに小さいため、**「もっと大きいインタプリタでは通用するのか」**という懸念は妥当です。
以前
yk が機能していなかったため他プロジェクトへ移ろうとしましたが、最近の LLM による実験結果や自身の進捗により、もう一度取り組む価値があると判断し、CPython を対象に検証を行いました。
2. ベンチマーク結果:ykcpython vs cpython/jit4
ykcpython(コミット 31b4d387)のベンチマーク結果を示します。AWFY スイートで測定され、各数値はプロセス実行を 30 回繰り返した平均および 99% 信頼区間です。
| ベンチマーク | cpython | cpython_jit | ykcpython |
|---|---|---|---|
| Bounce | 19.27±0.20 | 10.09±0.04 | 11.09±0.05 |
| CD | 32.21±0.23 | 26.10±0.08 | 29.33±0.16 |
| DeltaBlue | 7.43±0.02 | 6.21±0.02 | 7.62±0.04 |
| Json | 11.81±0.08 | 10.93±0.05 | 9.98±0.03 |
| List | 15.01±0.22 | 8.10±0.05 | 10.44±0.09 |
| Mandelbrot | 11.66±0.28 | 6.94±0.27 | 4.20±0.07 |
| NBody | 18.97±0.13 | 12.77±0.12 | 10.98±0.02 |
| Permute | 22.97±0.34 | 17.95±0.13 | 21.04±0.05 |
| Queens | 13.18±0.14 | 9.65±0.11 | 8.32±0.03 |
| Richards | 54.80±0.20 | 40.56±0.40 | 33.18±0.17 |
| Sieve | 23.34±0.06 | 17.02±0.07 | 13.74±0.06 |
| Storage | 22.03±0.19 | 19.94±0.08 | 19.67±0.04 |
| Towers | 20.46±0.25 | 12.65±0.05 | 13.67±0.04 |
結果の要約
- cpython: 通常の CPython 3.15.1 版。
- cpython_jit: 実験的な JIT(JIT4)を有効化した CPython 3.15.1 版。
- ykcpython:
を適用した CPython 3.15.1 版。yk- DeltaBlue を除く全てのベンチマークで通常の CPython より高速です(平均約 2.5% 遅い程度)。
- 13 つのベンチマーク中 8 つで、実験的な JIT を有効化した CPython よりも高速です。
注意: この数値は「実用上利用可能」や「全てのパフォーマンス向上を保証」という主張ではありません。ベンチマークでの高速化は通過すべき「ハードル」であり、「ゴールそのもの」ではありません。
3. yk とインタプリタの適応化について
yk の仕組み
- 入力: 軽量に修正された C インタープリタ。
- 出力: メータースレーシング JIT コンパイラを搭載した VM。
- 目的: 完全な言語実装からの書き換えではなく、既存のインタプリタをわずかに高速化しつつ互換性を維持すること(フォーク)。
- 手法:
- インタープリタ内の「ホットループ」を特定し C コードを実行記録。
- トレースを最適化してマシンコードに変換。
を介してインタープリタ側が潜在的なループ位置を告知(オプションだが推奨)。YkLocation
適応化の課題とアプローチ
- 課題: インタープリタ自体を熟知していない場合、一度に全てを行うのは困難。
- 戦略: シンプルな変更から始め、最小限の変更で JIT の起動を達成する。
- ビルドシステムの理解。
- 適切な
の配置。YkLocation - テストスイートの動作保証(保守性を高める)。
単純なインタプリタなら 10 LoC 未満で JIT が稼働しますが、CPython 等大型化インプタータではさらに作業が必要です。
4. CPython への適応プロセス
ステップ 1: チェックポイントの選定
- 初期調査では「ティア 1(Tier 1)インタープリタ」(標準的な実行器)を採用しましたが、いくつかの問題が浮上しました。
- 欠けていた機能(原子操作、sub のビット幅など)。
- 互換性問題。
- バグ(スタックマップ修正など)。
- これらの汎用的な修正は約半週間かかりました。
ステップ 2: 「クイッキニング」との相性問題
- CPython は「ジェネリック」オペコードを「専用」オペコードに書き換える**クイッキニング(specialisation)**機能を持っていました。
- 問題:
の単純な実装では、クイッキニングによる状態遷移がトレースを破棄し、コスト以上のパフォーマンス向上を生んでいませんでした。yk
ステップ 3: チア 2 (Tier 2) インタープリタへの移行
- CPython は実験的な JIT(JIT4)のために「ティア 2」インタープリタを用意しています。
- 移行決定: ティア 1 よりもティア 2 インタープリタが yk との相性が良かったため。
- ティア 1 を取り払い、ティア 2 に
サポートを追加。yk - 実装コストはわずか 80 LoC で済みました。
- ティア 1 を取り払い、ティア 2 に
初期バージョンの結果と最適化ヒント
初期の
ykcpython は CPython より遅い状態でしたが、以下の最適化ヒントを適用しました。
冪等関数(Idempotent Functions)の利用
インタプリタディスパッチにおける「メモリからオペコードの種類を読み取る」処理は不変であるため、これを冪等関数として
yk に認識させます。これにより関数呼び出しを削除し定数結果に置換できます。
// 冪等関数の定義 __attribute__((yk_idempotent)) uint16_t idempotent_load_opcode(uint64_t code_id, const _PyUOpInstruction *pc) { __asm__ volatile("" : "+r"(code_id)); return pc->opcode; } // インタプリタループ内の使用例 yk_promote(next_uop); yk_promote_code_id(yk_code_id); uopcode = idempotent_load_opcode(yk_code_id, next_uop);
この変更により、マンドルブロートベンチマークで 2.7 倍の高速化(約 30 LoC の変更)が達成されました。
5. 詳細な最適化テクニック
(1) トレース開始地点の追加
CPython のすべての実行器に
_JUMP_TO_TOP オペコードを持たないため、初期段階からトレースされていませんでした。実行器の先頭に YkLocation を追加することで、即座にトレースを開始させました。
(2) アンロール(Unrolling)
yk はデフォルトでループを含む関数をアウトライン化(トレースから除外)しますが、引数数が一定の関数など特定ケースではインライン化を希望します。
- 手法:
属性を追加し、意図的に展開させる。yk_unroll - 対象例:
,_PyFrame_Initialize
等。_PyFrame_ClearLocals
(3) 間接インライン化(Indirect Inlining)
間接呼び出し(ポインタ経由の関数呼び出し)はデフォルトでトレースできませんが、定数のポインタであれば可能にできます。
-
基本パターン:
function_type f = ...; if (!yk_is_interpreting()) { // 解釈中は true (削除される) yk_promote(f); } (f)(...); -
間接呼び出しの対応:
を使用し、静的にトレース可能な経路を指示します。yk_indirect_inline- 例: リスト/文字列の長さを取得する
のようなコード。_sq_length
- 例: リスト/文字列の長さを取得する
(4) アウトライン除外(yk_outline)
ガーベッジコレクション等の動的なチェックを含む関数(例:
_PyObject_GC_Link)は、トレースを失敗させる可能性があります。これらを yk_outline でマークすることで、呼び出し自体をトレース対象から外しました。
6. 結論と考察
達成されたこと
- CPython への適用成功: 約 450 LoC の追加コードで機能し、ベンチマークスイートにおいて CPython や実験的 JIT と遜色ない結果を出しました。
- パフォーマンス向上:
ベンチマークでは、11.52s から 10.85s に改善。json- 特定の最適化(間接インライン化など)によりさらに高速化を達成。
今後の課題と限界
- 過学習の回避: 一部のベンチマーク(例:
)では特定の条件で性能が低下するため、注意が必要です。deltablue - 万能ではない: 幅広いプログラムのパフォーマンス向上を保証するものではありません。
- ボトルネックの存在: HIR パターンの最適化や JIT コンパイラ自体の改善余地は残っています。
yk の強み
- 汎用性:
で発見された欠陥やバグは、他のインタプリタの適応にも役立ちます。ykcpython - ゼロからの再開発ではない: 既存の高性能な JIT ツールキットを再利用できるため、短時間で大きな成果を出せます。
謝辞 本研究には Edd Barrett、Lukas Diekmann の貢献、Shopify と英国王立工学アカデミーの資金提供、Chris Seaton のサポートに感謝します。