CPython 3.15のJIT、再び軌道に乗る
(blog.python.org)主な成果
| プラットフォーム | JITの性能向上(vs テールコーリング・インタープリタ) |
|---|---|
| macOS AArch64 | +11〜12% |
| x86_64 Linux | +5〜6% |
ベンチマークの範囲: 20%遅くなるケースから100%以上速くなるケースまでさまざま(
unpack_sequenceマイクロベンチマークを除く)
- 目標達成: 3.15の目標(5%向上)を1年以上前倒しで達成
- フリースレッディング対応: まだ未完成で、3.15/3.16を目標に作業中
重要な教訓
-
スポンサー離脱 = 危機 → コミュニティ主導へ転換
Faster CPythonチームの主要スポンサーが2025年に撤退したにもかかわらず、コミュニティの自発的な貢献によって、むしろ貢献者が増え、成果を上げました。 -
複雑な問題は細かく分割する
JITのように参入障壁の高いプロジェクトでも、作業単位の分解と明確なガイドの提供により、Cプログラマとしてのキャリアを持つ非専門家でも貢献できるようになりました。 -
バス係数(bus factor)の低下は健全なプロジェクトの指標
中間段階(optimizer)の貢献者は2人から4人に増え、非コア開発者も中核的な貢献者へと成長しました。 -
計測インフラが開発速度を変える
毎日JIT性能をレポートするシステム(doesjitgobrrr.com)が、回帰(regression)の早期発見とモチベーション維持の両面で決定的でした。 -
コミュニティ間の交流が技術力を高める
PyPyチームとの交流や、コンパイラ開発者との非公式なチャットが、実際の技術的進歩につながりました。
背景と成果
[IMG] JIT性能グラフ(2026年3月17日時点、低いほどインタープリタより高速)
(2026年3月17日時点のJIT性能。低いほどインタープリタ比で高速。画像出典: doesjitgobrrr.com)
朗報です。macOS AArch64では目標より1年以上早く、x86_64 Linuxでは数か月早く、CPython JITの(非常に控えめな)性能目標を達成しました。3.15アルファのJITは、macOS AArch64でテールコーリング・インタープリタより約11〜12%速く、x86_64 Linuxで標準インタープリタより5〜6%速くなっています。この数値は幾何平均であり、暫定値です。実際の範囲は20%遅くなるケースから100%以上速くなるケースまでさまざまです(unpack_sequenceマイクロベンチマークを除く)。まだ本格的なフリースレッディング対応はありませんが、3.15/3.16での対応を目指しています。JITはついに再び軌道に乗りました。
これがどれほど困難だったかは、どれだけ強調してもしすぎることはありません。 JITプロジェクトが本当に意味のある速度向上を達成できるのか、真剣に疑問視していた時期がありました。振り返ると、当初のCPython JITには実質的な速度向上がほとんどありませんでした。8か月前、私は3.13と3.14の初期のCPython JITが、むしろインタープリタより遅いことが多いというJIT回顧記事を書きました。その時期は、Faster CPythonチームが主要スポンサーの支援を失った時期でもありました。私はボランティアなので直接的な影響は受けませんでしたが、そこで働く同僚たちには影響があり、一時はJITの将来が不透明に見えました。
では、3.13や3.14と何が違ったのでしょうか。私たちの機転でJITを失敗の危機から救い出したという英雄譚を語るつもりはありません。現在の成功の大半は、正直に言って運のおかげだと思っています。適切な時期、適切な場所、適切な人、適切な選択。中核的なJIT貢献者であるSavannah Ostrowski、Mark Shannon、Diego Russo、Brandt Bucher、そして私のうち、誰か1人でも欠けていたら、これは実現しなかったと本気で思います。他の活発なJIT貢献者も漏らさないよう、さらに何人か挙げておきます: Hai Zhu、Zheaoli、Tomas Roun、Reiden Ong、Donghee Na、そしてほかにも名前を挙げ損ねた方々がいるはずです。
ここでは、JITであまり語られない人と、少しの運について話したいと思います。技術的な詳細に興味があれば、こちらを参照してください。
パート1: コミュニティ主導のJIT
Faster CPythonチームは2025年に主要スポンサーを失いました。私はすぐにコミュニティ主導で運営する案を提案しました。当時、それがうまくいくかどうかはかなり不透明でした。JITプロジェクトは、新規貢献者にとって向いていないことで知られています。歴史的に、多くの事前知識が必要だったからです。
ケンブリッジで開かれたCPythonコアスプリントで、JITコアチームは集まり、3.15までに5%高速なJIT、3.16までに10%高速なJITとフリースレッディング対応を実現するための計画を策定しました。あまり注目されませんでしたが、プロジェクトの健全性に不可欠だったのが、バス係数の低減でした。JITの3段階(フロントエンドのリージョンセレクタ、ミドルエンドのオプティマイザ、バックエンドのコードジェネレータ)のすべてで、2人以上のアクティブなメンテナを持つことを目指しました。
以前は、JITミドルエンドには活発に繰り返し貢献する人がたった2人しかいませんでした。現在では、JITミドルエンドに4人の活発な反復的貢献者がおり、非コア開発者2人(Hai ZhuとReiden)が有能で貴重なメンバーへと成長しました。
人を引き込むのに効果的だったのは、ごく一般的なソフトウェアエンジニアリングの実践でした。つまり、複雑な問題を管理可能な部分に分解することです。Brandtは3.14でこれを先に始めており、JIT最適化を単純な作業に分ける複数のメガイシューを立てました。たとえば「JITで単一の命令を最適化してみよう」といった形です。私はBrandtのアイデアを引き継ぎ、3.15でも適用しました。幸運にも、私が担当したのはより簡単な作業で、インタープリタ命令を簡単に最適化できる形へ変換するイシューでした。新しい貢献者を後押しするため、非常に詳細な手順をすぐ実行できる形で整理しました。また、作業単位も明確に区切りました。これが効果を発揮したようです。私を含め11人の貢献者がそのイシューに取り組み、ほぼインタープリタ全体をJITオプティマイザ向きの形へ変換しました。重要なのは、JITを不透明な塊から、JIT経験のないCプログラマでも貢献できるものへと分解したことです。
その他に効果的だったこととして、人を励まし、大きな成果も小さな成果も祝うことがありました。すべてのJIT PRには明確な成果物があり、それが人々に方向感を与えたのだと思います。
コミュニティによる最適化の取り組みは成果を上げました。JITはx86_64 Linuxで1%高速な状態から3〜4%高速な状態へ進歩しました(下の青い線を参照):
[IMG] コミュニティ最適化期間中のJIT性能 vs インタープリタ
(画像出典: doesjitgobrrr.com)
パート2: 幸運な選択
トレース記録(Trace Recording)
繰り返しになりますが、この大半は運のおかげだと思っています。ケンブリッジのCPythonコアスプリントで、Brandtが私を説得し、JITフロントエンドをトレーシング方式で書き直すことになりました。最初は気が進みませんでしたが、半ば意地で「動かないことを証明するために」書き直してみようと思ったのです。
最初のプロトタイプは3日で動きましたが、テストスイートを通しつつ、きちんとJITとして機能させるまでには1か月かかりました。初期結果はひどいものでした。x86_64 Linuxで約6%遅かったのです。諦めかけたそのとき、幸運な偶然が起きました。私はMarkの提案を誤解していたのです。
Markは、インタープリタにディスパッチテーブルをスレッド接続し、2つのディスパッチテーブル(通常のインタープリタ用とトレーシング用)を持たせようと提案していました。ところが私はそれを誤解して、さらに極端な版を作ってしまいました。通常命令のトレーシング版を用意する代わりに、トレーシングを担当する命令を1つだけ置き、2つ目のテーブル内のすべての命令がそれを指すようにしたのです。これが実に良い選択でした。当初の二重テーブル方式はインタープリタのサイズを2倍にしてしまい、コード肥大化と自然な速度低下を招いて、はるかに遅かったのです。命令1つと2つのテーブルだけを使うことで、インタープリタサイズの増加を命令1個分に抑え、基本インタープリタを非常に高速なまま維持できました。私はこの仕組みを愛着を込めて**デュアルディスパッチ(dual dispatch)**と呼んでいます。
トレース記録がどれほど重要だったかを示す数字があります。JITコードカバレッジは50%増加しました。これは、将来のあらゆる最適化が(単純化して言えば)50%ぶん効果を失っていたはずだということを意味します。
BrandtとMarkが、この素晴らしい解決策に偶然たどり着けるよう導いてくれたことに感謝します。
参照カウント削減(Reference Count Elimination)
もう1つの幸運な選択は、参照カウント削減を試してみたことでした。これはもともとMatt PageがCPythonバイトコードオプティマイザで行っていた作業です。私は、バイトコードオプティマイザの作業があっても、参照カウント減算のたびにJIT済みコード内へ分岐(branch)が1つ残っていることに気づきました。そこで「この分岐を消したらどうなるだろう」と考えたのですが、どれだけ効果があるかはまったく分かりませんでした。実際には、たった1つの分岐でもかなり高コストで、すべてのPython命令ごとに1つ以上の分岐があるなら、それらは積み重なっていきます。
さらに幸運だったのは、これがどれほど並列化しやすく、そして人々にインタープリタとJITを教えるための優れた教材になったかという点です。これが、Python 3.15のJITで人々に作業を割り当てる際の主要な最適化テーマになりました。作業の大半は手作業によるリファクタリングでしたが、JITの中心的な部分に圧倒されることなく学べる機会にもなりました。
パート3: 素晴らしいチーム
私たちには素晴らしいインフラチームがあります。実際には1人です。正確に言えば、私たちの「チーム」は現在、Savannahのクローゼットで動いている4台のマシンです。それでもSavannahは、JITのためにインフラチーム全体に匹敵する仕事を成し遂げました。性能数値を報告する手段がなければ、JITはこれほど速く前進できなかったでしょう。 毎日のJIT実行結果は、フィードバックループにおけるゲームチェンジャーでした。JIT性能の回帰を捉える助けとなり、私たちの最適化が本当に機能していることを確認できるようにしてくれました。
Markは技術的に卓越しています。インターネットはすでに彼に十分すぎるほどの称賛を送っていると思うので、これ以上は言わないでおきます :)。
Diegoも素晴らしいです。ARMハードウェア上のJITを担当しており、最近ではJITをプロファイラに優しいものにする作業も始めました。これがどれほど難しい問題かは、どれだけ強調してもしすぎることはありません。
Brandtはマシンコード・バックエンドの元となる基盤を築きました。これがなければ、新しい貢献者はアセンブラを書く必要があり、おそらくさらに多くの人を遠ざけていたでしょう。
パート4: 人と話すこと
人と話し、アイデアを共有することの価値を強調したいと思います。
PyPyについて多くを教えてくれたCF Bolz-Tereickに感謝します。私は数か月にわたってPyPyのソースコードを見て回り、それによってJIT開発者として全体的により良くなれたと思っています。CFは、助けが必要なときにとても親切に対応してくれました。
私はMax Bernsteinと気楽なコンパイラ雑談を続けていますが、これがなければとっくにモチベーションを失っていたでしょう。Maxは多作な書き手であり、親しみやすいコンパイラの専門家です。
アイデアは孤立した空間の中だけに存在するものではありません。しばらくのあいだコンパイラ開発者たちと交流してきたことで、JITを書くのが以前よりうまくなったと感じています。少なくとも、PyPyを見て回ったことは私の視野を広げてくれました!
結論
人が重要です。そして少しの運が加われば、JIT go brrr。
まだコメントはありません。