
2025/12/25 22:02
Windows x86‑64 用の Python 3.15 インタプリタは、ほぼ 15 %速くなる見込みです。
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
Summary
Ken Jinは、macOS AArch64(XCode Clang)およびWindows x86‑64(MSVC)のCPythonのターミナル呼び出しインタープリターに関する以前の実績主張を部分的に撤回したものの、測定可能な速度向上を報告しています。macOSでは約5 %、Windowsでは実験的な内部MSVCビルドで最大15–16 %の改善が確認されています。ベンチマークは、「tail‑call threaded」インタープリターが従来のcomputed‑gotoループよりも優れていることを示しています。現代のコンパイラではその差は縮小しますが、実験的なVS 2026ビルドでは幾何平均で約16 %の利得が確認されています。この改善は、短いインタープリター・ループによりコンパイラがヘルパー関数(例:
PyStackRef_CLOSE_SPECIALIZED)をインライン化し、レジスタ圧力を減らすことから生じています。
ターミナル呼び出しは、Josh HabermanのProtobufブログとHaoran XuによるClangの
__attribute__((musttail)) を使用したコピー&パッチ手法で広まりました。XCode Clangが修正されたCPython 3.14/3.15ではmacOS上で約5 %の速度向上が示され、Python 3.15「What's New」には長いスクリプトに対して最大40 %の速度向上が記載されています。MSVCチーム(Chris Eibl、Brandt Bucher)がリリースしたVisual Studio 2026はターミナル呼び出しをサポートし、具体的な利得をもたらします:spectralnorm_tc 1.48倍速、nbody_tc 1.35倍速、bm_django_template_tc 1.18倍速、xdsl_tc 1.14倍速。
速度向上はPython 3.15まで継続すると予想されます。機能がロールバックされない限り、macOSのバイナリにはターミナル呼び出しが有効化された状態で配布され、VS 2026を使用したWindowsビルドでも同様の利得が期待できます。CPythonコミュニティはさらにビルドフラグ(
--tail-call-interp)を洗練させ、プロファイルガイド付き最適化(PGO)を統合して性能を向上させる可能性があります。
CPUバウンドのPythonワークロード(科学計算、ウェブフレームワークなど)を実行するユーザーは、わずかな速度改善に気付くかもしれません。Pythonバイナリを配布する企業はコード変更なしで高速な実行ファイルを提供でき、Visual Studioを使用するWindowsの開発者もランタイム効率の向上から恩恵を受けるでしょう。
本文
Ken Jin
2025年12月24日
以前、Python の tail‑calling に関するパフォーマンス結果を謝罪した記事を書きました。
コンパイラにバグが発生していることに気づかずに実績を伝えてしまった点を詫びたのです。
本日、誠に光栄ながらその謝罪を部分的に撤回できると確信しています――ただし対象は macOS AArch64(XCode Clang)と Windows x86‑64(MSVC)の2つだけです。
自社実験では、CPython の tail‑calling インタープリタが XCode Clang でビルドした AArch64 macOS 上の pyperformance で computed‑goto インタープリタより 5 %、Windows 上の実験的 MSVC バージョンでは約 15 % 優れていました。
Windows のビルドは switch‑case インタープリタに対して行われますが、理論上は大きな差になるべきではありません(次節で詳しく)。
もちろんこれは「正確」に近い結果だと自負しています。もっと慎重に確認しましたが、完璧ではありません。ただし、早めに共有し失敗を公言することで、自分のコードにバグがあることを他人に指摘してもらえるケースが多く、今後も同様の姿勢で発信していきます。
また、Python 3.15 の開発サイクル内でこの変更が取り消されない前提です。
インタープリタの簡単な背景
C ベースのインタープリタを書く主流は2つあります。
Switch‑case
switch (opcode) { case INST_1: … case INST_2: … }
オペコードに応じて適切なハンドラへ分岐します。
Computed Goto(GCC/Clang 拡張)
goto *dispatch_table[opcode]; INST_1: … INST_2: …
次のラベルのアドレスへジャンプするだけで、switch‑case よりも「ジャンプ数が 1 つ少ない」ことがメリットです。
しかし近年はコンパイラとハードウェアの進化により、この差はほぼ無視できるほど縮小しました。Nelson Elhage の調査では、modern Clang で computed‑goto が switch‑case より高いスピードアップを示すことはわずかな単純数値(低位の1桁)に留まっています。
Call / Tail‑Call スレッド化インタープリタ
数十年前から提案されていたが、実装上難点があった方式です。
各バイトコードハンドラを個別関数とし、次の命令へ tail‑call で移行します。
return dispatch_table[opcode]; PyObject *INST_1(...) { … } PyObject *INST_2(...) { … }
C では tail‑call 最適化は「最適化」でしかなく、実際に起きるかどうかはコンパイラ次第です。
Clang は
__attribute__((musttail)) を導入し、tail‑call を強制できるようになりました。Josh Haberman の Protobuf ブログで初めてメインストリームのインタープリタへ採用されました。後に Haoran Xu は GHC 呼び出し規約と組み合わせて効率的なコードを生成できることを発見し、Copy‑and‑Patch と呼ばれる手法で JIT のベースラインに採用しました。
現状
固定 XCode Clang を使用した CPython 3.14/3.15 のパフォーマンスは、tail‑calling インタープリタが computed‑goto より 5 % 程度の modest な向上を示します。
uv は macOS 用に Python 3.14 を tail‑call 付きで配布しており、その効果が一部見られると考えています。公式 3.15 macOS バイナリでも同様の実装を予定しています。
ただし、この記事は主に MSVC Windows x86‑64 に焦点を当てていますので、次節で詳細を述べます。
Windows での Tail‑Calling
注意
以下の MSVC 機能は実験的です。MSVC チームが継続するかどうかに依存します。自己責任でご利用ください!
以下は CPython を MSVC で tail‑call 対応(TC)と switch‑case(SC)の pyperformance ベンチマーク結果です。
1.00× を超える値がスピードアップ、1.00× 未満は遅延を示します。
平均で約 15–16 % の向上が確認され、一部のベンチマークでは最大 78 % の加速、逆に 60 % 程度の低下もあります。大部分は改善です。
警告
これらは実験的内部 MSVC コンパイラで測定したものです。以下が公開版結果です。
Visual Studio 2026 を使用して再確認しました。下記表は本件の具体例です。
| Benchmark | No TC (ms ± std dev) | With TC (ms ± std dev) | Speedup |
|---|---|---|---|
| spectralnorm_tc_no | 146 ± 1 | 98.3 ± 1.1 | 1.48× |
| nbody_tc_no | 145 ± 2 | 107 ± 2 | 1.35× |
| bm_django_template_tc_no | 26.9 ± 0.5 | 22.8 ± 0.4 | 1.18× |
| xdsl_tc_no | 64.2 ± 1.6 | 56.1 ± 1.5 | 1.14× |
実際に 14 % 程度の向上が確認できます。小規模なマイクロベンチマークではさらに大きい効果が出ています。
Chris Eibl と Brandt Bucher の貢献で PR が完了し、MSVC チームへの感謝も述べられます。Visual Studio 2026 のリリースをお祝いします。
3.15 の What’s New に掲載されている記載は以下の通りです:
Visual Studio 2026 (MSVC 18) を使用したビルドは、新しい tail‑calling インタープリタを利用できます。Windows x86‑64 で switch‑case インタープリタに対し、pyperformance の幾何平均で約 15 % のスピードアップが報告されています。大規模な純 Python ライブラリでは 15 %、長時間実行される小さなスクリプトでは 40 % の加速を観測しています。(Chris Eibl、Ken Jin、Brandt Bucher が貢献、MSVC チームへの特別謝辞)
スピードアップの源泉は?
tail‑calling インタープリタが得られる高速化は「レジスタ使用効率の向上」だと考えていましたが、実際には CPython で主な理由ではないようです。
現在の推測は、tail‑call がコンパイラのヒューリスティクスを「正常値」に戻し、最適化が有効に働く点にあります。
CPython 3.15 のインタープリタループは約 12k 行 C コードで構成されており、switch‑case と computed‑goto 両方とも同じ関数内に収められています。
この大きさはコンパイラの多くのヒューリスティクスを破壊し、インライン化が妨げられる原因となります。実際に 12k 行関数では最適化対象として扱われず、簡単なヘルパー関数も inlined されないケースがあります。
tail‑call を有効にすると、コンパイラは関数を tail‑call として認識し、各小さなヘルパー(例:
_PyLong_CheckExactAndCompact 等)がインライン化されます。これによりアセンブリコードが圧縮され、実行時オーバーヘッドが減少します。
具体例
BINARY_OP_ADD_INT の処理を抜粋します(switch‑case 版):
TARGET(BINARY_OP_ADD_INT) { /* Increment the instruction pointer. */ _Py_CODEUNIT* const this_instr = next_instr; frame->instr_ptr = next_instr; next_instr += 6; _PyStackRef right = stack_pointer[-1]; /* Check that LHS is an int. */ PyObject *value_o = PyStackRef_AsPyObjectBorrow(left); if (!_PyLong_CheckExactAndCompact(value_o)) { JUMP_TO_PREDICTED(BINARY_OP); } /* ... */ }
VS 2026 の switch‑case 版では
_PyLong_CheckExactAndCompact や PyStackRef_CLOSE_SPECIALIZED 等がインライン化されません。tail‑call を有効にすると、コンパイラはこれらを inlined し、アセンブリコードは数命令で済みます。
PGO ビルドでも同様の挙動が確認されています(上記リンク参照)。
試してみるには
現在はソースからビルドする必要があります。VS 2026 で CPython をクローン後、以下を実行してください:
$env:PlatformToolset = "v145" ./PCbuild/build.bat --tail-call-interp -c Release -p x64 --pgo
Python 3.15 の開発が安定すれば、公式バイナリとして配布できるようになるでしょう。
補足と編集
クロスコンパイラテストの依頼に応じて、pystones の簡易ベンチマークを作成しました。全構成で PGO を有効化しています。
| Compiler | PlatformToolSet | Pystones/second (高いほど良い) |
|---|---|---|
| VS2019 | 142 | 677 544 |
| VS2022 | 143 | 710 773 |
| VS2026 | 145 | 682 089 |
| VS2026+TC | 145 | 970 306 |
この toy ベンチマークでは約 30 % の向上が確認できます。
※科学的根拠は薄く、サンプル数は 1 である点、および Windows ラップトップの Turbo Boost を無効化できなかった点をご了承ください。