2025年3月: Python のGILと3.13の実験的な新機能「free threading」を知る¶
福田(@JunyaFff)です。 今月の「Python Monthly Topics」は、Python 3.13の新機能「free threading」について解説します。
はじめに¶
2024年10月にリリースされた Python 3.13 。その中でもっとも注目すべき実験的な新機能の「free threading」について紹介します。 本記事では「free threading」について紹介するにあたり、避けては通れない「Global Interpreter Lock(以下GIL)」というCPythonのロック機構の基本を説明して、「free threading」についての概要と動作検証した結果を紹介します。
Python 3.13 での他の新機能については先月の記事「Python 3.13で更新された機能の紹介」 をご参照ください。
なお、今回の記事を書くにあたり参考にしたドキュメントは下記になります。
GILとはなにか、なにが問題だったのか¶
GILの基本概念¶
GILとは、複数のスレッドが同時にメモリにアクセスすることがないように、複数のスレッドでPythonインタープリターを同時に実行することを防ぐためのものです。 簡単にいうと、Pythonプログラムは複数のスレッドを作成できるものの、一度に実際のPythonコードを実行できるのは1つのスレッドだけになります。
Pythonの並列処理で多くの方が制約として認識しているのが、このGILというロック機構です。
GILの利点¶
このGILというロック機構には、いくつかの大きな利点があります。
メモリ管理の簡素化: Pythonのガベージコレクションとメモリ管理を簡単かつ安全に実装
シングルスレッドパフォーマンスの最適化: 複雑なロック機構を排除することでシングルスレッドの実行速度を向上
C拡張モジュールとの互換性: C拡張モジュールがスレッドセーフではないことを考慮するため、GILによって安全性を確保
GILがもたらす制約¶
その反面、GILがあることによって、Pythonの並列処理では以下のような制約が生じていました。
真の並列処理ができない: マルチコアCPUが当たり前の現代において、Pythonは複数のコアを効率的に活用できない
CPU負荷の高い処理が遅い: 計算集約型のタスクを並列化してもほとんど恩恵が得られない
並列処理のためには複雑な回避策が必要: マルチプロセスによる並列化や、C拡張を使った実装を行う必要がある
これらの問題を解決するため、Python開発チームは長年にわたってGILの撤廃や改良を検討してきました。そして、Python 3.13でついにfree threadingという機能が実験的に導入されました。
free threadingとは¶
free threadingの技術的概要¶
3.13のfree threadingは、CPythonからGILを完全に排除するのではなく、オプションとして無効化できる設計になっています。これにより、後方互換性を維持しつつ、並列処理のメリットを享受できるようになるための実験的な機能です。
技術的な特徴は以下の通りです。
より細かい単位で制御できる[1] のロック機構: GILの代わりに、必要なオブジェクトごとに細かいロック機構を導入
並行ガベージコレクション: 複数のスレッドでPythonプログラムが同時に実行されても安全にメモリ管理ができるガベージコレクション
アトミック参照カウント: オブジェクトの参照カウントを原子的に操作することで、オブジェクトの整合性を保ちながらスレッド間の競合を回避
バイアスロック: 頻繁にアクセスされるオブジェクトに対して、特定のスレッドが優先的にロックを獲得できる最適化
GILが解決していた問題を別の方法で解決することで、Pythonの並列処理の制約を取り除くことができるようになっています。 全体のロックから、細かいロック機構を採用することで、スレッド間の競合を最小限に抑えつつ、並列処理の実現が可能になります。
詳細は PEP 703 – Making the Global Interpreter Lock Optional in CPython を参照してください。
Python 3.13 free threading のインストール方法¶
では実際にfree threadingを体験してみましょう。以下の方法で利用が可能です。
方法1: 公式ビルドをダウンロード¶
Python.orgからビルド済みのインストーラーをダウンロードし、オプションとなっている「Free-threaded Python [experimental]」を有効にし、インストールできます。
Python Downloads から最新のPython 3.13をダウンロード
インストーラーを実行してインストール
customizeを選択
Free-threaded Python [experimental] を選択しインストールを行う
python3.13t(Windowsであれば、python3.13t.exe)がインストールされます。3.13tがfree threading版のPythonです。
方法2: uvを使用する¶
過去の記事(2024年9月: さらなる進化を遂げた「uv」の新機能)でも紹介した「uv」を使用して、free threading版のPythonをインストールすることもできます。
$ uv python install 3.13t
Installed Python 3.13.2 in 4.62s
+ cpython-3.13.2+freethreaded-macos-aarch64-none
またインストールせずに uv run
コマンドでも利用できます。
$ uv run --python 3.13t sample.py
なおuv のインストールについてはこちらをご参照ください。
方法3: ソースコードからビルド¶
ビルドオプション「--disable-gil」を有効にし、ソースコードからビルドすることも可能です。
# 関連ライブラリの更新/インストール
$ sudo apt update
$ sudo apt install build-essential zlib1g-dev \
> libncurses5-dev libgdbm-dev libnss3-dev libssl-dev \
> libreadline-dev libffi-dev curl clang
# Python 3.13.2をダウンロード
$ curl -O https://www.python.org/ftp/python/3.13.2/Python-3.13.2.tgz
$ tar zxvf Python-3.13.2.tgz
$ cd Python-3.13.2
$ ./configure --disable-gil
$ make
$ make install
他のコンパイルオプションについては、適宜変更してください。
筆者の環境ではよく利用されるオプション、 --enable-optimizations
と --with-lto
を指定してビルドできることを確認しました。
動作確認¶
筆者の環境では、公式よりダウンロードしインストールしたものを利用します。 インストーラーを利用した場合には、python3.13t が free-threading 版のPythonです。
$ python3.13t
Python 3.13.2 experimental free-threading build (main, Feb 12 2025, 15:02:03) [Clang 19.1.6 ] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
Python 3.13のfree threadingでの動作検証¶
実際にfree threadingを使った並列処理のコード例を見ていきます。
動作環境¶
動作環境は以下のとおりです。
Apple M2
Sequoia 15.3.2
Python 3.13.2 / Python 3.13.2t
CPUバウンドな処理をマルチスレッドで実行してみる¶
Pythonのスレッドを concurrent.futures
の ThreadPoolExecutor
を使って実行します。
実行するサンプルコードは以下のとおりです。
1億回繰り返す計算処理をスレッド4つで実行しています。
import concurrent.futures
import time
def cpu_bound_task() -> int:
result = 0
for i in range(10**8): # 1億回繰り返す
result += i % 10
return result
def main():
start = time.time()
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
for f in [executor.submit(cpu_bound_task) for _ in range(4)]:
f.result()
end = time.time()
print(f"Time taken: {end - start:.2f} seconds")
if __name__ == "__main__":
main()
上記のコードをPython 3.13で実行した結果は以下の通りです。
$ python3.13 example_thread.py
Time taken: 11.51 seconds
次に、free threading版で実行してみましょう。高速化されているのがわかります。 3.13では1スレッドずつしか動作しないため遅いですが、free threading版では4スレッドが同時に動くため、速くなっていることがわかります。
$ python3.13t example_thread.py
Time taken: 3.57 seconds
CPUバウンドな処理をマルチプロセスで実行してみる¶
さて同じ処理をマルチプロセスを使って実行してみます。先ほどのコードの ThreadPoolExecutor
を ProcessPoolExecutor
変えるだけでマルチスレッドをマルチプロセスに変えて実行できます。
import concurrent.futures
import time
def cpu_bound_task() -> int:
result = 0
for i in range(10**8): # 1億回繰り返す
result += i % 10
return result
def main():
start = time.time()
with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:
for f in [executor.submit(cpu_bound_task) for _ in range(4)]:
f.result()
end = time.time()
print(f"Time taken: {end - start:.2f} seconds")
if __name__ == "__main__":
main()
マルチスレッドの例と同様に3.13と3.13tでそれぞれ実行してみます。結果に大きな差はありませんでした。 前述のとおり、free threadingはGILを排除することが目的であり、マルチプロセスはGILの影響を受けないため、マルチプロセスを利用する場合にはfree threadingの効果が現れにくいことがわかりました。
$ python3.13 example_process.py
Time taken: 3.42 seconds
$ python3.13t example_process.py
Time taken: 3.57 seconds
さて、続いて想定どおり結果に差がでたマルチスレッドで実行する処理を変えて試してみましょう。
先ほどの example_thread.py
の cpu_bound_task
関数を変えて、どのように変化するか確認してみます。
試したパターン(すべて ThreadPoolExecutor + 4並列)¶
座標を生成して距離を計算する
def cpu_bound_task():
"""座標を生成して距離を計算する"""
points = [(x, x + 1) for x in range(1_000_000)] # 100万個の座標を生成
total = 0.0
for x, y in points:
total += (x ** 2 + y ** 2) ** 0.5
return total
辞書アクセスを含む計算
def cpu_bound_task():
"""辞書アクセスを含む計算"""
items = [{"x": i, "y": i + 1} for i in range(1_000_000)] # 100万個の辞書を生成
total = 0.0
for item in items:
total += (item["x"] ** 2 + item["y"] ** 2) ** 0.5
return total
文字列生成を含む処理
def cpu_bound_task():
"""文字列生成を含むCPUバウンドなタスク"""
total = 0
for i in range(10_000_000): # 1000万回繰り返す
# 文字列を生成して長さを計算
s = f"item-{i}"
total += len(s)
return total
それぞれ実行してみたところ、以下のような結果になりました。生成処理やdictやlistへのアクセスを含む処理の場合、free threading版のほうが遅い結果となりました。
処理内容 |
ファイル名 |
3.13(GILあり) |
3.13t(Free Threading) |
---|---|---|---|
座標生成+距離計算 |
|
0.75 秒 |
24.51 秒 |
辞書のリスト生成+処理 |
|
0.92 秒 |
25.37 秒 |
f-string + len()(文字列) |
|
3.64 秒 |
3.95 秒 |
なぜ遅い場合があるのか¶
free threading版のPython 3.13では既知の課題があります。
シングルスレッドの性能低下: free threadingを有効にすると、シングルスレッドの実行時に約40%の性能低下が見られる
メモリ使用量の増加: 細粒度のロック機構やImmortal objects[2] などの実装により、メモリ使用量が増加
シングルスレッドでの性能低下を確認するため、以下のコードの実行時間を比較してみましょう。
import time
start = time.time()
items = [{"x": i, "y": i + 1} for i in range(1_000_000)] # 100万個の辞書を生成
end = time.time()
print(f"Time taken: {end - start:.2f} seconds")
試してみたところ、既知の課題どおり、free threadingが遅いことがわかりました。
$ python3.13 example_dict.py
Time taken: 0.08 seconds
$ python3.13t example_dict.py
Time taken: 2.21 seconds
今後の改善の方向性と新しい Python 3.14での対応¶
このような課題に対して、今後のPythonでは以下のような改善が予定されています。
シングルスレッド性能の向上: 現在のパフォーマンス低下は主にPEP 659: specializing adaptive interpreterが無効になっていることが原因。Python 3.14ではthread-safeな方法でこれを再有効化する予定
既知の制限の解消: Immortal objectsによるメモリ使用量の増加などの既知の制限事項は、今後のリリースで対処されていく予定
今後の予定や改善点の詳細については、以下のリソースを参照してください。
2025年3月時点での最新のアルファ版である、3.14.0 alpha 6が3月14日にリリースされているため、試してみましょう。 注意事項として、3.14はまだアルファ版であり、安定性やパフォーマンスが保証されていないため、実行結果は参考程度にしてください。
先ほどと同じ3種類のスクリプトをPython 3.140a6で実行した結果が下記になります。 1つ目のスクリプトは、3.14/3.14tどちらも遅くなってしまいましたが、2つ目、3つ目のスクリプトは3.14tに大幅に改善が見られています。
処理内容 |
ファイル名 |
3.13(GILあり) |
3.13t(Free Threading) |
3.14(GILあり) |
3.14t(Free Threading) |
---|---|---|---|---|---|
座標生成+距離計算 |
|
0.75 秒 |
24.51 秒 |
5.19 秒 |
21.32 秒 |
辞書のリスト生成+処理 |
|
0.92 秒 |
25.37 秒 |
1.40 秒 |
0.64 秒 ✅ |
f-string + len()(文字列) |
|
3.64 秒 |
3.95 秒 |
3.16 秒 |
0.83 秒 ✅ |
また、今回の記事ではふれていませんが、free threading版では、同じスレッドで作成されたオブジェクトに対してはロックが最小限になるように最適化されています。 そのため、別スレッドで対応したオブジェクトを更新しようとすると、ロックが発生し、処理が遅くなることがあります。 詳細については Python 3.13の新機能(その1) PEP 703: フリースレッドモード も合わせてご参照ください。
free threading対応状況¶
free threadingは実験的な機能であり、すべてのライブラリが対応しているわけではありません。とくにC拡張モジュールや、GILに依存しているライブラリは注意が必要です。 free threadingに対応しているかどうか、以下のリソースを活用して確認してみましょう。
Compatibility Status Tracking - py-free-threading: free threading対応しているOSSの一覧
pytest-freethreaded - GitHub: free threadingに対応しているかどうかpytestで確認できるツール
まとめ¶
Python 3.13で追加されたfree threading。この機能により、マルチコアCPUの能力を活かした真の並列処理が今後可能になります。
本記事で見てきたように、CPUバウンドな処理においてfree threadingを有効にすることで、従来のマルチスレッド処理が高速化されることを確認できました。 これにより、Pythonでの並列処理の選択肢が広がり、特定のユースケースではマルチプロセスを使わずにマルチスレッドで高いパフォーマンスを得られるようになります。
ただし、実験的な機能であり課題も残っているため、プロダクション環境での採用は慎重に検討する必要があります。また、利用するライブラリのfree threading対応状況も確認しておくことも重要です。
PEPでは、段階的な移行を目指していると書かれています。長期的には、ビルド時オプションからランタイム制御へと移行し、最終的にはGILがデフォルトで無効になることが期待されています。 今後のPythonのリリースでfree threadingがさらに改良され、最終的にはGILのない状態がデフォルトになる日も来るかもしれません。今後もキャッチアップしていきましょう。