Slide 1

Slide 1 text

Pythonの実行はどこまで賢くなったのか? CPythonとPyPyから見る最適化のしくみ PyCon JP 2026.8.21 (DAY1) 11:45 - 12:30 Recustomer株式会社 CTO Shugo Manabe @curekoshimizu

Slide 2

Slide 2 text

Profile : 眞鍋 秀悟 ( X: @curekoshimizu ) 略歴 ● ● ● ● ● ● 京都大学 / 大学院 ○ 入試一位合格 Fixstars ○ Executive Engineer Mujin ○ Architect Preferred Networks ○ Engineering Manager Hacobu ○ 研究開発部部長・CTO室室長 [Now] Recustomer ○ CTO ○ かなり長い間 Pythonを 業務で使ってきた CTO of the Year 2025, Audience Award 2

Slide 3

Slide 3 text

時は2020年 (Python 3.9頃) 3

Slide 4

Slide 4 text

Pythonエンジニアの 望みは? 4

Slide 5

Slide 5 text

5

Slide 6

Slide 6 text

6

Slide 7

Slide 7 text

1位:型 3.10 : X | Y 3.11 : Self 3.12 : class Foo[T] 3.13 : class Foo[T = int] 3.14 : 型アノテーション遅延評価 3.15予定 : TypeForm (型そのものの型付け) 各バージョンで確実に進化 7

Slide 8

Slide 8 text

2位:性能改善 3位:並行・並列改善 速くしてほしいという願いの多さを感じる 8

Slide 9

Slide 9 text

Python features への 要望と歩み 性能改善 Shannon Plan 並行・並列 改善 GIL無効化 9

Slide 10

Slide 10 text

性能改善 並行・並列 改善 NoGIL について 昨年 PyCon JP 2025 で 登壇させていただきました ので そちらに関する高速化のお話 Shannon Plan は今回は割愛します GIL無効化 10

Slide 11

Slide 11 text

今日のお話はこちら! 「並列化せずとも」 速くなっていった CPythonのお話 Python features への 要望と歩み 性能改善 Shannon Plan 並行・並列 改善 GIL無効化 11

Slide 12

Slide 12 text

2020年 Shannon Plan 発足 12

Slide 13

Slide 13 text

雑にいうと 13

Slide 14

Slide 14 text

CPython 超高速化計画 ~ Faster CPython ~ 14

Slide 15

Slide 15 text

Shannon Plan とは? 15

Slide 16

Slide 16 text

毎version 1.5倍ずつ 高速化して 16

Slide 17

Slide 17 text

4回リリースしたら 5倍高速化だ! プロジェクト 17

Slide 18

Slide 18 text

Shannonによる faster-cpython プロジェクト 1.5 ** 4 ≈ 5 18

Slide 19

Slide 19 text

adaptive specializing interpreter 特殊化適応型インタプリタ 19

Slide 20

Slide 20 text

JIT 20

Slide 21

Slide 21 text

その計画は どうなったか? 21

Slide 22

Slide 22 text

CPython 3.11 (2022年) 特殊化適応型インタプリタ リリース完了! 22

Slide 23

Slide 23 text

Python 3.13 (2024年) experimental-JIT (実行にはCPythonのビルドを自分でする必要あり) 23

Slide 24

Slide 24 text

Python 3.14 (2025年) JIT機能を含んだ バイナリをリリース (Windows・Macは環境変数 PYTHON_JIT=1 を有効化すれ ば試せるバイナリ) 24

Slide 25

Slide 25 text

❯ uv run --python 3.14 python Python 3.14.5 (main, May 10 2026, 19:28:16) [Clang 22.1.3 ] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import sys >>> sys._jit.is_available() True >>> sys._jit.is_enabled() False Python 3.14 JITは利用可能だが 有効にはなっていない 25

Slide 26

Slide 26 text

❯ PYTHON_JIT=1 uv run --python 3.14 python Python 3.14.5 (main, May 10 2026, 19:28:16) [Clang 22.1.3 ] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import sys >>> sys._jit.is_available() True >>> sys._jit.is_enabled() True PYTHON_JIT=1 をつけて 有効にできる 26

Slide 27

Slide 27 text

Python 3.13/3.14 どちらも JITはデフォルトで ONではないことに注意 27

Slide 28

Slide 28 text

Python 3.10からの 毎リリース 1.5倍が目標! 28

Slide 29

Slide 29 text

結果どれくらい 速くなったの? 29

Slide 30

Slide 30 text

Python 3.10 ↓ Python 3.11 30

Slide 31

Slide 31 text

Python 3.10 ↓ 1.25倍 Python 3.11 31

Slide 32

Slide 32 text

Python 3.10 ↓ 1.25倍 Python 3.11 特殊化適応型インタプリタありがとう 32

Slide 33

Slide 33 text

Python 3.11 ↓ Python 3.12 33

Slide 34

Slide 34 text

Python 3.11 ↓ ? Python 3.12 34

Slide 35

Slide 35 text

Python 3.11以降 〇〇倍高速化されたとは What's New In Python 3.xでは 語られなくなった... 35

Slide 36

Slide 36 text

公式リリースでは 言っていなくとも 36

Slide 37

Slide 37 text

Faster CPython チーム提供 ベンチマークみてみよう 37

Slide 38

Slide 38 text

Faster CPython チームが公開しているベンチマーク pyperformance 幾何平均|出典: faster-cpython/benchmarking-public Python 3.10比 1.46倍 Faster CPython チーム 公開している ベンチマーク 38

Slide 39

Slide 39 text

Faster CPython チーム公開しているベンチマーク pyperformance 幾何平均|出典: faster-cpython/benchmarking-public 毎回1.5倍の積み重ねで 最終的に5倍という目標は明らかに未達 Faster CPython チーム 公開している ベンチマーク 39

Slide 40

Slide 40 text

Faster CPython チーム公開しているベンチマーク pyperformance 幾何平均|出典: faster-cpython/benchmarking-public それでも何もコードを変えていない のに速くなっているのは確か! Faster CPython チーム 公開している ベンチマーク 40

Slide 41

Slide 41 text

私のもっている実機環境では? 41

Slide 42

Slide 42 text

本日お話する題材コード: フィボナッチ数列 42

Slide 43

Slide 43 text

フィボナッチ数列の計算 def fib(n): return n if n < 2 else fib(n - 1) + fib(n - 2) 43

Slide 44

Slide 44 text

fib(43)の速度比較 2.5倍 44 AMD Ryzen 7 8700G・Ubuntu 24.04LTS・RAM 128GB・CPythonは uv ビルド(python-build-standalone)

Slide 45

Slide 45 text

皆さん! 45

Slide 46

Slide 46 text

確実に 46

Slide 47

Slide 47 text

確実に確実に 47

Slide 48

Slide 48 text

Pythonは 毎バージョン 速くなっているんです 48

Slide 49

Slide 49 text

2025年のサーベイ Python 3.13/3.14 あわせて 使用率 17% … 出典:https://blog.jetbrains.com/pycharm/2025/08/the-state-of-python-2025/ 49

Slide 50

Slide 50 text

重要 versionを上げて 高速化の恩恵を 受けよう! 50

Slide 51

Slide 51 text

1. 高速化のお話の前に Pythonコードは何をしているのか? 51

Slide 52

Slide 52 text

Pythonファイル を 実行すると 何が起きているか? 52

Slide 53

Slide 53 text

フィボナッチ数列の計算 def fib(n): return n if n < 2 else fib(n - 1) + fib(n - 2) 本日はずっとこの例 53

Slide 54

Slide 54 text

def fib(n): return n if n < 2 else fib(n - 1) + fib(n - 2) ソースコード ast.parse AST 54

Slide 55

Slide 55 text

AST (構文木) 55

Slide 56

Slide 56 text

+の意味は まだ 決まっていない ● “a” + “b” ● 1+2 ● 1.0 + 2.0 など、 実行時に + の意味が決まる 56

Slide 57

Slide 57 text

ソースコード AST バイトコード >>> fib.__code__.co_code 1命令 = 1B(命令番号) + 1B(引数) 80 00 → RESUME 56 00 → LOAD_FAST_BORROW n 5e 02 → LOAD_SMALL_INT 2 38 12 → COMPARE_OP < 64 03 → POP_JUMP_IF_FALSE 80 00 56 00 5e 02 38 12 00 00 64 03 00 00 1c 00 56 00 23 00 5c 01 00 00 00 00 00 00 00 00 56 00 5e 01 2c 0a 00 00 00 00 00 00 00 00 00 00 34 01 00 00 00 00 00 00 5c 01 00 00 00 00 00 00 00 00 56 00 5e 02 2c 0a 00 00 00 00 00 00 00 00 00 00 34 01 00 00 00 00 00 00 2c 00 00 00 00 00 00 00 00 00 00 00 23 00 ※ 3.11以降は、命令の後ろに特殊化用の CACHE 領域(2バイト単位)が続く —— 右の 00 00 の連なりがその正体 57

Slide 58

Slide 58 text

.pyc の中身を覗くと —— 同じバイト列が、そのまま居る $ python3 -c "import fib; print(fib.fib.__code__.co_code[:16].hex(' '))" 80 00 56 00 5e 02 38 12 00 00 64 03 00 00 1c 00 $ xxd -g 1 -s 82 -l 16 __pycache__/fib.cpython-314.pyc 00000052: 80 00 56 00 5e 02 38 12 00 00 64 03 00 00 1c 00 ..V.^.8...d..... Pythonでよく見かける __pycache__/lib.cpython-314.pyc というようなファイルはこの意味! 58

Slide 59

Slide 59 text

ちなみに —— コンパイル結果は .pyc にキャッシュされる 初回 fib.py → コンパイル → バイトコード → .pyc に保存 __pycache__/fib.cpython-314.pyc 2回目 以降 fib.py → .pyc を読み込むだけ(コンパイル省略) → バイトコード .pyc があるとこの部分が速くなる 59

Slide 60

Slide 60 text

人間が読みやすい ようにする方法 : 逆アセンブラ >>> dis.dis(fib) 上からVMが順次実行 60

Slide 61

Slide 61 text

Virtual Machine (VM) ● スタックマシン : CPython・Java・Wasm ● レジスタマシン:Lua・CPU CPythonはスタックマシンでバイトコードを実行している 61

Slide 62

Slide 62 text

条件分岐 fib(n=5) の場合の if (n < 2) の部分の動作 メモリー n=5 Load Compare (<) POP_JUMP_IF_FALSE 2 5 stack 5 False 62

Slide 63

Slide 63 text

fib(8) の計算の最後のスタックマシンの状態 fib(8) = fib(7) + fib(6) の計算 BINARY_OP (+) fib(6) = 8 8 fib(7) = 13 13 21 stack 63

Slide 64

Slide 64 text

fib(8) の計算の最後のスタックマシンの状態 fib(8) = fib(7) + fib(6) の計算 BINARY_OP (+) fib(6) = 8 8 fib(7) = 13 13 21 stack ここあっさり 13 + 8 = 21 をやっているが VMの中はそんなに簡単じゃない 64

Slide 65

Slide 65 text

BINARY_OP (+) は全然簡単じゃない Python にとって + という演算は 引数によって色んな意味をもっている 65

Slide 66

Slide 66 text

BINARY_OP (+) は全然簡単じゃない Python にとって + という演算は 引数によって色んな意味をもっている ● ● ● ● ● 1+2 “a” + “b” 1.0 + 2.0 Fraction(1, 10) + Fraction(1, 20) np.array([1, 2, 3]) + np.array([4, 5, 6]) 66

Slide 67

Slide 67 text

BINARY_OP (+) は全然簡単じゃない Python にとって + という演算は 引数によって色んな意味をもっている ● ● ● ● ● 1+2 “a” + “b” 1.0 + 2.0 Fraction(1, 10) + Fraction(1, 20) np.array([1, 2, 3]) + np.array([4, 5, 6]) さらに __add__ という関数自体も 実行時に書き換えられている可能性も考慮する必要あり (スクリプト言語ならではの宿命) 67

Slide 68

Slide 68 text

BINARY_OP (+) の詳しい仕組みに ついては PyCon JP 2024 登壇内容をご覧ください 68

Slide 69

Slide 69 text

何用の足し算かを探しに探して 整数型用の足し算だとわかる だから 13 + 8 = 21 69

Slide 70

Slide 70 text

2. 先ほど紹介していた fib(43) の計算 は どうして速くなったのか? 70

Slide 71

Slide 71 text

fib(43) は 何回の足し算を していますか? def fib(n): return n if n < 2 else fib(n - 1) + fib(n - 712)

Slide 72

Slide 72 text

7億回 正解: 約 足し算がとても多いサンプルコード 72

Slide 73

Slide 73 text

BINARY_OP (+) は とても大変な作業をしていることは 解説しました 73

Slide 74

Slide 74 text

BINARY_OP (+) を 整数 + 整数 じゃないかもしれない? と思いながらやるのは 非効率 74

Slide 75

Slide 75 text

今まで 特殊化適応型 インタプリタ 何用の足し算かわからないので 探しに探して 整数型用の足し算だとわかる。 だから 13 + 8 = 21 毎回整数+整数なので 整数用の足し算を準備しておき 13, 8 が両方とも整数。 だから 13 + 8 = 21 75

Slide 76

Slide 76 text

特殊化適応型インタプリタ は 逆アセンブラの変化 からも 動いているのがわかる 76

Slide 77

Slide 77 text

複数回 fib を実行したあとの dis.dis(fib, adaptive=True) の結果 3 4 RESUME 0 LOAD_FAST_BORROW 0 (n) LOAD_SMALL_INT 2 COMPARE_OP 18 (bool(<)) POP_JUMP_IF_FALSE 3 (to L1) NOT_TAKEN LOAD_FAST_BORROW 0 (n) RETURN_VALUE L1: LOAD_GLOBAL 1 (fib + NULL) LOAD_FAST_BORROW 0 (n) LOAD_SMALL_INT 1 BINARY_OP 10 (-) CALL 1 LOAD_GLOBAL 1 (fib + NULL) LOAD_FAST_BORROW 0 (n) LOAD_SMALL_INT 2 BINARY_OP 10 (-) CALL 1 BINARY_OP 0 (+) RETURN_VALUE 3 4 RESUME_CHECK 0 特殊化適応型 インタプリタ発動後 LOAD_FAST_BORROW 0 (n) LOAD_SMALL_INT 2 COMPARE_OP_INT 18 (bool(<)) POP_JUMP_IF_FALSE 3 (to L1) NOT_TAKEN LOAD_FAST_BORROW 0 (n) RETURN_VALUE L1: LOAD_GLOBAL_MODULE 1 (fib + NULL) LOAD_FAST_BORROW 0 (n) LOAD_SMALL_INT 1 BINARY_OP_SUBTRACT_INT 10 (-) CALL_PY_EXACT_ARGS 1 LOAD_GLOBAL_MODULE 1 (fib + NULL) LOAD_FAST_BORROW 0 (n) LOAD_SMALL_INT 2 BINARY_OP_SUBTRACT_INT 10 (-) CALL_PY_EXACT_ARGS 1 BINARY_OP_ADD_INT 0 (+) RETURN_VALUE 色々なところが 特殊化されている! 77

Slide 78

Slide 78 text

ちなみに 途中で fib(8.0) と 浮動小数点数を 混ぜると? 78

Slide 79

Slide 79 text

ちゃんと特殊化 COMPARE_OP_INT から汎用命令 COMPARE_OP に戻る 79

Slide 80

Slide 80 text

特殊化適応型インタプリタのお話でした 80

Slide 81

Slide 81 text

特殊化適応型インタプリタ あくまでも 命令1個ずつの最適化 (一部super-instructionというのもあるが) 81

Slide 82

Slide 82 text

特殊化適応型インタプリタ は バイトコードを置き換える技術 であり CPUが直接実行するネイティブな機械語 を生成するわけではない 82

Slide 83

Slide 83 text

つまり 特殊化適応型インタプリタ JITではない JITのお話を 始めていきます 83

Slide 84

Slide 84 text

3. JIT (Just-in-time) ~ 3.13で実験導入・3.14で同梱 ~ 84

Slide 85

Slide 85 text

JIT とは? 実行開始 AOT(Ahead-Of-Time) (Rust / C / Go など) 事前に全部 コンパイル 高速実行 インタプリタ 低速実行 (CPython など) JIT (Just-In-Time) 低速実行 ホット部分を 実行時にコンパイル 高速実行 (PyPy / Java など) 時間 85

Slide 86

Slide 86 text

JIT の強み 静的コンパイラが持っていない 実行時の情報を使って 最適化できる! 86

Slide 87

Slide 87 text

JIT の弱み コンパイル時間も 実行時間に含まれる 強力な最適化処理時間をやりすぎると その遅さを取り戻すことができない可能性 87

Slide 88

Slide 88 text

もちろん過去にも CPython に JIT を入れる試みは たくさんありました! 88

Slide 89

Slide 89 text

CPython への JIT、挫折の20年 挑戦の歴史 (代表的なものを) 2002 2010年代 Psyco Pyston / Cinder 2009 Unladen Swallow 挑戦と挫折 転機 本体マージ 89

Slide 90

Slide 90 text

公式の挑戦記録 PEP 3146 (2010) Unladen Swallow の CPython 統合提案(Google のチーム・公式 PEP) “This is similar to the approach taken by the current breed of JavaScript engines (V8, SquirrelFish Extreme) …” 「これは現世代の JavaScript エンジンや多くの Java VM と同じアプローチだ」 公式PEP化するものの 撤退が決まった 90

Slide 91

Slide 91 text

コア開発者の告白 (2012) “yet another” JIT compiler の挑戦の数々... “I would like to write yet another JIT compiler for CPython. … I did not understand exactly why Unladen Swallow and psyco projects failed, so please tell me if you think that my project is going to fail too!” 「“また一つ”JITコンパイラを書きたい。Unladen Swallow や Psyco がなぜ失敗したのか、 正直よく分からない。——これも失敗すると思うなら、教えてほしい」 Victor Stinner — CPython コア開発者 python-dev(公式開発者ML)2012年7月17日 mail.python.org/pipermail/python-dev/2012-July/120991.html 91

Slide 92

Slide 92 text

一方その頃、 他の言語でJIT化は成功 していたのであった... 92

Slide 93

Slide 93 text

JIT は他では「当たり前」になっていた ブラウザから16年遅れ 1984 2002 2008 2018 Smalltalk C# / .NET JavaScript 元祖・実用JIT 最初からJIT Ruby Chrome の V8 MJIT→YJIT 他言語の JITの歴史 1999 2005 2012 2020 Java Lua Julia PHP 8.0 HotSpot VM LuaJIT 生まれつきJIT JIT搭載 Python JIT挑戦中 (失敗) 2002 Psyco 2010年代 2009 Pyston / Cinder Unladen Swallow 2024 Python 3.13・実験的 93

Slide 94

Slide 94 text

「なぜ Python だけ できないのか?」 揶揄され続けた20年間 94

Slide 95

Slide 95 text

「なぜ JIT が無いのか」の声 2012 python-dev(CPython公式開発者ML)・Victor Stinner(コア開発者)・2012-07-17 “I would like to write yet another JIT compiler for CPython. … tell me if you think that my project is going to fail too!” 「“また一つ”JITコンパイラを書きたい。…これも失敗すると思うなら教えてくれ」 2016 Hacker News(技術フォーラム)・2016-02-18・item 11125769 “Adding a freaking JIT to CPython is a ridiculous suggestion that will never happen.” 「JITなんて馬鹿げた提案、絶対に実現しない」 2019 Hacker News・2019-09-12・item 20952620 “Is there any reason there couldn’t be a python interpreter as awesome as V8 is for JavaScript?” 「V8並みにすごいPythonインタプリタが作れない理由って何?」 2020 Hacker News・2020-09-24・item 24577040 “Could you tell us why CPython still doesn’t have JIT?” 「なぜCPythonには“まだ”JITが無いのか教えてくれ」 95

Slide 96

Slide 96 text

当事者 Mark Shannon の言葉 Shannon Plan の方 “CPython is slow. We all know that, yet little is done to fix it.” 「CPythonは遅い。みんな知っている。なのに、ほぼ何もされてこなかった」 Mark Shannon — CPython コア開発者・のち Faster CPython チーム技術リード python-dev(公式開発者ML)2020年10月20日 = Shannon Plan の書き出し mail.python.org/archives/list/[email protected](2020-10-20) 96

Slide 97

Slide 97 text

なぜ JIT は失敗し続けたのか? 1. 以前からある C API 互換を壊せない 2. 効果が不安定 3. 保守コストが大変 97

Slide 98

Slide 98 text

C API を使っている Numpy 等のライブラリが 動かなくなるような 互換性を無視した大改革は厳しい 98

Slide 99

Slide 99 text

重いJITは実行時コンパイル時間 で負ける。 軽いJITは最適化効果で負ける。 99

Slide 100

Slide 100 text

Psyco の例で その保守の難しさを ざっくり説明 100

Slide 101

Slide 101 text

“C をコンパイルすれば機械語になる”のに、なぜ JIT が要るのか ビルド時 —— C コンパイラが作る 実行時 —— JIT が作る “汎用”の機械語(=インタプリタ) “専用”の機械語 どんな .py が来ても動かせる。その代わり毎回: このプログラム・この型(int)専用。だから: fetch → switch → 箱を pop → 型チェック → 足す → 箱を確保 → push a + b 1回 = 数百命令 jo slowpath add r1, r2 ; あふれた時だけ汎用へ a + b 1回 = 2命令 あなたの .py は、インタプリタの“入力データ” —— C コンパイル時にはまだ存在しない。だから: 専用化は、実行時にしかできない。差は “C 対 機械語” ではなく “汎用 対 専用” 101

Slide 102

Slide 102 text

なぜ“もう1本”書くことになるのか —— “+” の意味は1箇所にしかない 手書き JIT が int+int の機械語を吐くには /* ceval.c —— “+” の意味の定義はここだけ */ w = POP(); v = POP(); if (int と int) { 足す。あふれたら多倍長へ昇格 } else if (str と str) { 連結 } else { __add__ / __radd__ を呼ぶ } PUSH(result); 「型ガードを吐け」 int か確認が要る —— と知っている必要がある 「箱から出して ADD 1命令」 オブジェクトの箱の構造 —— を知っている必要がある 「あふれたら多倍長へ飛べ」 wrap しない、という “+” の意味の核心 —— も知っている インタプリタ = 意味の“唯一の”定義書 必要な知識 = 左の定義書、まるごと全部 だから JIT は、“意味の写し”を機械語生成の形でもう一度書くことになる —— そして2つが一致しているかは、誰も検証してくれない

Slide 103

Slide 103 text

JIT を“手で書く”とは、どういう作業か ① ② ③ インタプリタと“同じ意味”の機械語生成器を、もう1本書く C のインタプリタとは別に、命令ごとの「x86 を吐くコード」を手書き —— 型分岐・例外・参照カウントまで正確に。1つズレれば、黙って間違う 対象は、掛け算で増える 約150の全命令 × CPU(x86 / ARM64…)× ガード・脱最適化・レジスタ割り当て —— Psyco が x86 専用で終わった理由 Python の進化に、永遠に追従 毎バージョン、命令は増え・変わる —— インタプリタ側の変更を JIT 側へも手で移植し続ける、終わりのない“二重保守” 同じ“Python の意味”を、2つの形で保守し続ける —— CPython 本体に JIT が入ったのも、この“手書き”をやめられたから(copy-and-patch) 103

Slide 104

Slide 104 text

Psyco の中の a + b Psyco のソースコード抜粋(..... は中略) /* ① c/Objects/pintobject.c(実物・抜粋) */ static vinfo_t* pint_add(PsycoObject* po, vinfo_t* v, vinfo_t* w) { CONVERT_TO_LONG(v, a); /* int? 検査+箱外し */ x = integer_add(po, a, b, true); ..... /* overflow → CPython の nb_add へ退避 */ /* ② c/i386/iencoding.c */ BEGIN_CODE COPY_IN_REG(v1, rg); /* MOV rg,(v1) */ COMMON_INSTR_FROM(group, rg, /* ADD rg,(v2) */ v2->source); END_CODE if (ovf && runtime_condition_f(po, CC_O)) return NULL; /* あふれ検知も自前 */ ① 型ガードも“退避路”も、自分で設計 int か検査して箱外し、あふれたら CPython へ退避 —— 型の“意味の再実装”だけで 約9,200行 ② レジスタ割り当ても、あふれ検知も自前 ADD 1命令のために MOV・フラグ管理・レジスタ確保 —— コード生成の共通土台で 約5,900行 ③ 命令バイトを、1バイトずつ直書き 0x83 は ADD の命令番号。ModRM バイトも手計算 —— x86 一式 約3,000行のうち、生バイト書きは 約1,500 行 /* ③ c/i386/iencoding.h */ code[1] = 0xC0 | (group<<3) | (rg); code[2] = (code_t) _v; code[0] = 0x83; code += 3; ..... 104

Slide 105

Slide 105 text

単価 17行 × 88実装 a + b(int+int)の1実装ぶん 約100行 (本体17行+ヘルパ・定型) × 88実装(演算 × 型) 意味の再実装ぶん ≈ 9,200行 c/Objects/ の実測値 int・float・str・list・dict… 40ファイル さらに「命令」ごとに作る必要あり 105

Slide 106

Slide 106 text

JIT 手書きの“見積書” CPUアーキテクチャ個数 x86 32bit だけの保守だけでこの量 意味の再実装(演算×型 88実装) 9,200 行 バイトコード命令ごとの特殊化 3,200 行 コード生成の土台(共通) 5,900 行 特殊化コア・フレーム管理 ほか 10,700 行 x86 依存ぶん一式(CPU 1つあたり) 3,000 行 合計 出典: psyco-1.6-src(SourceForge) 約 32,000 行 Pythonのデータの持ち方が変 わったりするたびに修正が必須 これを頑張ったが 力尽きたのが Psyco 106

Slide 107

Slide 107 text

Psyco の教訓 JITコード を “手で書き続ける”のは 保守が難しすぎる x86 だけでこの量になってしまった... 作者 Armin Rigo は保守を断念 —— のちに“機械に書かせる”道(PyPy)を、自ら作りに行く 107

Slide 108

Slide 108 text

素朴な疑問 専用のコード片を、 “事前に”作って 持っておけば良いのでは? —— 正解。でも、それが“出来る形”になるまで 20年かかった 108

Slide 109

Slide 109 text

転機: 2021年、1本の論文 Copy-and-Patch Compilation Xu & Kjolstad, 2021 109

Slide 110

Slide 110 text

重いコンパイルを実行時にしない、という逆転の発想 実行時に重いコンパイラを 走らせるから、遅いんだ! 110

Slide 111

Slide 111 text

CPythonビルド時に 部品機械語テンプレートを 全部作っておけばいい 命令(uop)ごとの機械語テンプレート =「stencil」 111

Slide 112

Slide 112 text

stencil =「部品機械語テンプレート」を 実行時に不定箇所だけ埋める (概念図) ビルド時に用意した stencil mov 実行できる機械語 rax, [rsp-8] test rax, rax add rax, 0xDEADBEEF jne 0xDEADBEEF 赤 = 埋めるべき「穴」 mov コピー& パッチ → rax, [rsp-8] test rax, rax add rax, 0x7f3a20 jne 0x55d1c8 黄 = 実行時の値で埋めた 112

Slide 113

Slide 113 text

機械語生成の最後は、 コピーと穴埋めだけ 重い compiler backend は、実行時には動かさない 113

Slide 114

Slide 114 text

C API/ABI を変えず 同じランタイムを使う (JIT専用の別ランタイムを作らない) 114

Slide 115

Slide 115 text

実行時のコンパイル時間は ほぼ問題にならないから 最適化をどんどん 積み上げれば今後速くなる! 115

Slide 116

Slide 116 text

保守するのは今まで通りの インタプリタのCコード それによる機械語テンプレート はビルド時に自動生成されるか ら二重管理になることはない 116

Slide 117

Slide 117 text

CPython 本体が 保守できる JIT にできた! → 悲願の Python 3.13 へ実験導入 117

Slide 118

Slide 118 text

20年かけて“保守できる形”へ 挑戦の歴史 2002 2010年代 2021 Psyco Pyston / Cinder Copy-and-Patch 論文 32bit専用で限界 本体外で発展 「本体が飲める形」 2009 2020 2024 Unladen Swallow Shannon Plan 3.13 実験JIT導入 LLVM採用/5倍目標は未達 JITはStage 3-4に明記 → 3.14 同梱・初期は無効 挑戦と挫折 転機 本体マージ 118

Slide 119

Slide 119 text

さて 119

Slide 120

Slide 120 text

ここまで紹介した 悲願のJITの実力を 見てみましょう! 120

Slide 121

Slide 121 text

fib(43)の速度比較 JIT なしの結果 121 AMD Ryzen 7 8700G・Ubuntu 24.04LTS・RAM 128GB・CPythonは uv ビルド(python-build-standalone)

Slide 122

Slide 122 text

fib(43)の速度比較 JIT ON / OFF を同一バージョンで比較 このfibでは、3.14 の JIT の効果は出ていない 122 AMD Ryzen 7 8700G・Ubuntu 24.04LTS・RAM 128GB・CPythonは uv ビルド(python-build-standalone)

Slide 123

Slide 123 text

あれ? 遅くなってない? 123

Slide 124

Slide 124 text

安心して 124

Slide 125

Slide 125 text

Python 3.14でも ちゃんと JIT で 速くなる ベンチマークもあります! 125

Slide 126

Slide 126 text

3.14 の JIT、公式計測でも“効く形”はある 公式ベンチ基盤 faster-cpython/benchmarking-public の JIT オン/オフ比較(ベンチ別) richards −17% deltablue −9% spectral_norm −5% nbody −2% go +12% ← 速くなる 遅くなる → 出典: faster-cpython/benchmarking-public(linux-x86_64・3.14.0a7+・commit 7363e8d・2025-05-03・JIT vs base) 全体(pyperformance の幾何平均)は 1.002x faster = ほぼ中立 —— “効く形”と“効かない形”がある 126

Slide 127

Slide 127 text

fib(43)の速度比較 3.15β:JIT で約14%高速化 127 AMD Ryzen 7 8700G・Ubuntu 24.04LTS・RAM 128GB・CPythonは uv ビルド(python-build-standalone)

Slide 128

Slide 128 text

よかった 正式リリースはまだなので リリース前のバージョン Python 3.15βは 改善されていそう! 128

Slide 129

Slide 129 text

今はJITの基盤が できたフェイズ と言える 129

Slide 130

Slide 130 text

CPython の JIT高速化 の 今後に注目して下さい! PEP 836 (2026): 次の目標は pyperformance で +20%(free-threaded 比) 130

Slide 131

Slide 131 text

ところで 131

Slide 132

Slide 132 text

実は Python コミュニティ には JIT の大先輩 がいます 132

Slide 133

Slide 133 text

その名も 133

Slide 134

Slide 134 text

PyPy 134

Slide 135

Slide 135 text

PyPy = 別実装のPython実行系 有名なPython実行系たち (Pythonファイルを実行できる) ● ● ● ● ● ● CPython : Pythonといえばこれ PyPy MicroPython Jython IronPython PyPy : 使い方は pypy3 main.py と打つだけで … CPython で動作していたものを動かせる 135

Slide 136

Slide 136 text

このPyPyの 速度の実力は? 136

Slide 137

Slide 137 text

fib(43)の速度比較 このfibでは、PyPy が CPython 3.11 の約10.7倍 137 AMD Ryzen 7 8700G・Ubuntu 24.04LTS・RAM 128GB・CPythonは uv ビルド(python-build-standalone)

Slide 138

Slide 138 text

PyPyは CPython 3.11より 3倍速いよ 138 出典: speed.pypy.org「How fast is PyPy3.11?」(2026年8月 閲覧)

Slide 139

Slide 139 text

PyPy 小史: JIT は2010年に実った 2003 〜2009 PyPy 誕生 JIT 生成器、3度失敗 「Python で Python を」 「もうすぐ出る」が、出ない 2004–07 2010 EU が研究資金 JIT 搭載(PyPy 1.2) FP6・28ヶ月の公費研究 speed.pypy.org 開設 現在は PyPy 7.3 Python 3.11 互換 139

Slide 140

Slide 140 text

PyPy の作者 = ? 140

Slide 141

Slide 141 text

Psyco 作者 Armin Rigo は PyPy 共同創始者の一人 141

Slide 142

Slide 142 text

「JIT手書きやめよう」 142

Slide 143

Slide 143 text

インタプリタ専用言語 RPython を開発 インタプリタ(解釈する係) それを書いた言語 CPython ceval.c などの C コード C 言語 PyPy pyopcode.py などの RPython で書かれたコード RPython (Restricted Python) RPython のコードは Python として動く。だが、普通の Python は RPython として実行できない 制限 (Restricted) 例:1つの変数に、複数の型を入れてはいけない/グローバルは定数扱い

Slide 144

Slide 144 text

RPython の仕組み (全体地図) : 保守簡単 RPythonで書かれた インタプリタ 型がすべて推論可能 translator (Pythonで書かれている) が JIT・GC 機能も追加して変換 RPythonコードはCPythonでも 動くコード体系なので CPython でテストもできる C コード (自動生成) pypy 実行バイナリ (JIT 内蔵)

Slide 145

Slide 145 text

RPythonで書いた インタプリタを書けば JITが自動で 作られるってこと 145

Slide 146

Slide 146 text

ということで 146

Slide 147

Slide 147 text

いきなりですが 147

Slide 148

Slide 148 text

とある プログラミング言語の インタプリタを 作ってJIT試そう! 148

Slide 149

Slide 149 text

brainfuck言語の インタプリタを RPythonで 書いてみましょ! 149

Slide 150

Slide 150 text

brainfuck言語 可読性・記述性が最悪だが コンパイラが小さくできることで有名な言語 150

Slide 151

Slide 151 text

書いてみた! 全93行 151 黄色の10行だけが JIT への指示(JitDriver のヒント)—— 残りの83行は、ただのインタプリタ

Slide 152

Slide 152 text

インタプリタだけ書いて JIT を手に入れる 1. brainfuck のインタプリタをRPython で書く 2. rpython --opt=jit bf.py 3. JIT付きバイナリを得る! brainfuck 用の JIT コンパイラは 何も書いていないことに注意! 152

Slide 153

Slide 153 text

mandelbrot.b の速度比較 インタプリタを CPython で動かす vs インタプリタをバイナリまで変換したもの (JITあり・なし) 153 AMD Ryzen 7 8700G・Ubuntu 24.04LTS・CPython 3.14.7・PyPy 7.3.23 の rpython で翻訳・mandelbrot.b は Erik Bosman

Slide 154

Slide 154 text

PyPy は JIT コンパイラ でもある “PyPy 1.0: JIT compilers for free and more” 「PyPy 1.0 —— JIT コンパイラを、タダで。ほかにも色々」 “PyPy is now also a Just-In-Time compiler generator.” (PyPy はもう、JIT の“生成器”でもある) doc.pypy.org/en/latest/release-1.0.0.html 154

Slide 155

Slide 155 text

確かにそうだったね! 155

Slide 156

Slide 156 text

発想の根本的な違い CPython の JIT PyPy(メタトレーシング) 観測するのは、 あなたのプログラム(fib) → fib 専用の機械語を作る 観測するのは、 「fib を実行中のインタプリタ」 → インタプリタごと特化した機械語 156

Slide 157

Slide 157 text

実は、CPython の JIT と同じ思想 機械語生成を手で書かない —— インタプリタの定義から、自動で得る CPython(copy-and-patch) PyPy(メタトレーシング) ビルド時に、インタプリタのソース (uop 定義)から機械語テンプレを生成 実行時に、インタプリタの動きを 観測して機械語化 違うのは「いつ・どうやって」だけ。「誰が書くか(機械が)」は同じ Armin Rigo たちは、同じ答えに 20年早く辿り着いていた 157

Slide 158

Slide 158 text

同じ言語、まったく別のエンジン CPython PyPy 実装 C を人間が手書き RPython で書き、C を自動生成 実行エンジン インタプリタ+JIT(3.13〜・実験) インタプリタ+JIT 標準装備(2010〜 ) メモリ管理 参照カウント(+循環 GC) 世代別トレーシング GC —— 参照カウン トは無い C API 本家(PyObject* そのもの) cpyext で変換 ——「弱点」の根はここ “Python の約束”は同じ。守り方が、全部違う 158

Slide 159

Slide 159 text

こんなに速いのに なぜみんな PyPy を使っていない? 159

Slide 160

Slide 160 text

PyPyのJITは 実は遅いの? 160

Slide 161

Slide 161 text

そんなことは 決してない 161

Slide 162

Slide 162 text

JIT の出来で、負けたのではない 勝負は“処理系の性能”ではなく、“CPython 互換性”だった 162

Slide 163

Slide 163 text

CPython の“別実装”とは 同じもの = “Python 互換” 違うもの = CPython の“中身” 文法・言語の意味・標準ライブラリ あなたの .py は、同じ結果で動く C API / ABI(PyObject*)・参照カウント バイトコード・メモリ上のオブジェクト配置 = 言語仕様が保証する範囲 = 誰も保証していない内部実装 「Python 互換」と「CPython 互換」は、別の約束 —— PyPy が名乗れるのは前者まで 163

Slide 164

Slide 164 text

PyPyは CPython互換ではなく あくまでも Python互換 164

Slide 165

Slide 165 text

弱点①: 色々なエコシステムは CPython の ABI の上にある 実はここが CPythonの 強みだったりする CPython あなたのコード → C API/ABI(本家) → NumPy・DB ドライバなど 大量の C 拡張 PyPy あなたのコード → cpyext(互換レイヤ) → 同じ C 拡張 動くが遅い PyPyはNumPyなどのエコシステムが 遅いという問題 165

Slide 166

Slide 166 text

実測: NumPy を挟むと PyPy は負ける 1.94秒 # 100要素の dot を 100万回 import numpy as np a = np.arange(100, dtype=np.float64) b = np.arange(100, dtype=np.float64) total = 0.0 for _ in range(1_000_000): total += float(np.dot(a, b)) 0.41秒 CPython 3.11 PyPy 7.3.22 (3.11互換) PyPy が 約4.7倍 遅い cpyext の往復コストが、JIT の速さを食い潰す JIT ウォームアップ後・3回実行の最小値・環境は fib(43) の計測と同一(AMD Ryzen 7 8700G・Ubuntu 24.04) 166

Slide 167

Slide 167 text

弱点②: 温まるまでが遅い JIT の助走が要るぶん、短命なスクリプトではむしろ損 —— 167

Slide 168

Slide 168 text

実測: 軽い仕事でも PyPy は負ける 38 ms # fib(25) を1回だけ計算して終了 def fib(n): return n if n < 2 else fib(n-1) + fib(n-2) 18 ms print(fib(25)) CPython 3.11 PyPy 7.3.22 PyPy が 約2倍 遅い 起動と JIT の助走を、回収する前に終わる プロセス起動〜終了の実時間・7回実行の最小値 | ※ fib(28)(約0.04秒)で逆転、fib(30) 以降は PyPy の圧勝 —— 分岐点を越えるかが全て 168

Slide 169

Slide 169 text

PyPy の使いどころ ◎ 純 Python のホットループが、長く回る処理 △ Web API ボトルネックが DB / IO なら、Python が3倍でも全体は数% × 今日のベンチマーク fib はこのタイプ だったので高速 C 拡張ヘビーな処理・短命プロセス Numpy を多く使う科学技術計算は cpyext の壁 169

Slide 170

Slide 170 text

特に有名なのが 競技プログラミング Pythonで競技プログラミング = PyPy PyPyのホットループとJITの速さがとても効く 170

Slide 171

Slide 171 text

ここまで JITの先輩 PyPy の お話をしましたが 171

Slide 172

Slide 172 text

JITといえば これも忘れてはいけない 172

Slide 173

Slide 173 text

Numba ライブラリ 173

Slide 174

Slide 174 text

Pythonコードで 高速化したい関数に @numba.njit をつける import numba @numba.njit def fib(n): return n if n < 2 else fib(n-1) + fib(n-2) 174

Slide 175

Slide 175 text

fib(43)の速度比較 Numba も入れた結果 175 AMD Ryzen 7 8700G・Ubuntu 24.04LTS・RAM 128GB・Numba は @njit 付与(初回コンパイル 0.37秒は別)

Slide 176

Slide 176 text

総和計算のベンチマーク # 0〜N-1 の総和 total = 0 for i in range(N): total += i 176

Slide 177

Slide 177 text

sum ループ(N = 10⁸)の速度比較 Numba 速すぎる! 177 total += i を1億回・4構成とも同じ答え・Numba は LLVM が N(N-1)/2 に閉形式化 = O(1)・環境は fib(43) と同一

Slide 178

Slide 178 text

Numba が こんなに 圧倒的に速いのは? 178

Slide 179

Slide 179 text

実は今回のやり方は ある意味 チートをしているから 179

Slide 180

Slide 180 text

Numba が使う LLVM は 数学公式 (ガウス和) を使って最適化している 180

Slide 181

Slide 181 text

つまり N(N-1)/2 の結果を出力して ループ自体消している。 他は1億回ループしている。 181

Slide 182

Slide 182 text

これは C言語 や Rust のような コンパイル言語で 当然に行われている 最適化技法 182

Slide 183

Slide 183 text

LLVM概要 gcc(C) clang(C) rustc(Rust) (独自言語) ↓ ↓ ↓ ↓ 専用最適化器 LLVM ↓ ↓ バイナリ バイナリ めっちゃ色々な 最適化手法が ここにあるので このエコシステムに載れると 独自最適化をかかなくていい 183

Slide 184

Slide 184 text

みんな、同じ LLVM clang(C) rustc(Rust) Numba(@njit) ↓ ↓ ↓ LLVM ↓ “証明”の最適化 —— ループを、答えの式 N(N-1)/2 に置き換える 184

Slide 185

Slide 185 text

ただし Numba も 難しいところがある 185

Slide 186

Slide 186 text

N = 10 ** 10 # 0〜N-1 の総和 total = 0 for i in range(N): total += i 49999999995000000000 (正解) -5340232226128654848 (不正解) CPython・PyPy Numba 186

Slide 187

Slide 187 text

Numba は エラーを吐かずに 間違いを犯した 計算結果(約5×10¹⁹)が 64bit整数の上限(約9.2×10¹⁸) を超えたため だまって桁あふれした (例外は発生しない) 187

Slide 188

Slide 188 text

Pythonは多倍長整数を扱えるが Numba が型推論で int64 に固定するため (LLVM に渡る前に決まっている) 188

Slide 189

Slide 189 text

Numba は “Python を速くする” というより “その関数だけ、Python をやめる” 型を確定 → Python の意味論を外す → LLVM が本気を出す 189

Slide 190

Slide 190 text

Numba は色々難しいポイントが色々 ● dict は制限付き(typed.Dict のみ) ● 文字列は使えるが遅いことが多い …. 色々不便はあるが それでも効くところには 効くJITライブラリ = numba 190

Slide 191

Slide 191 text

4. 3つのJITの違いがみえたところで CPython に戻ります (まとめ) 191

Slide 192

Slide 192 text

では、なぜ CPython は色々代替があるのに 自前の JIT を作るのか CPython という“土台”そのものが Python のプラットフォーム になったから PyPyやNumbaの例で わかった CPython の提案文書にも書いていて — 192

Slide 193

Slide 193 text

PEP 836 抜粋 However, many users cannot adopt an alternative runtime even when it may perform better on their code due to factors such as supported Python versions, extension compatibility, embedding requirements, deployment constraints, and tooling support. 「多くのユーザーは、たとえ速くても代替ランタイムへ移れない —— バージョン・拡張互換・組込み・デプロイ・ツールの理由で」 193

Slide 194

Slide 194 text

JavaScript の JIT V8 エンジン は 18年前 から続き CPython は今基盤ができたところ 194

Slide 195

Slide 195 text

CPython は まだまだ速くなる! 195

Slide 196

Slide 196 text

高速化への要望と近年の進捗 Python 3.13 JIT 実験的 free-threading (NoGIL) 実験的 Python 3.14 JIT 同梱! デフォルトはOFF free-threading (NoGIL) 同梱! デフォルトはOFF ※ 3.14 時点では free-threaded ビルド上で JIT は使えない —— 併用は次ページ PEP 836 のロードマップ 196

Slide 197

Slide 197 text

PEP 836 への期待! “not the JIT or free-threading, but rather, the JIT and free-threading” 「JIT か free-threading か、ではない。JIT と free-threading、だ」 “まだ賢くなる”は願望ではない —— 期限と数値つきで、いま審議中 PEP 836 “JIT Go Brrr” Draft・2026年7月2日(Ostrowski / Ken Jin / Bucher) 3.17 β1 までに free-threaded 上で幾何平均 +20% → “実験的”卒業を目指す。 3.16 で frontend をメソッド方式へ移す提案も柱 197

Slide 198

Slide 198 text

歴史がわかった ところで 198

Slide 199

Slide 199 text

CPython 高速化プロジェクトの 歴史的目撃者になりましょう! 199

Slide 200

Slide 200 text

エンジニア9名しかいない中 5名登壇! 200