RevComm Tech Blog

コミュニケーションを再発明し 人が人を想う社会を創る

GILを無効化したPythonを早速試してみた

バックエンドエンジニアの小門です。

この記事ではグローバルインタプリタロック (GIL) が解消されたPythonを動かしてみた検証の方法と結果について書きます。

なおGIL自体の説明や詳しい仕組みについてこの記事ではほとんど説明しないのでご了承ください。

準備として開発バージョンを取得してソースコードからビルドし、ビルド成果物のPythonランタイムを使って検証します。

準備(ビルド)

Pythonにおける「GIL廃止」の第一歩として、CPython本家のリポジトリにおいてGILを無効化できるようにするための修正が2024年3月12日mainブランチへマージされました。

gh-116167: Allow disabling the GIL with PYTHON_GIL=0 or -X gil=0 #116338

また同日、上記の変更を取り込んだ開発バージョンが v3.13.0a5 としてリリースされました。

https://www.python.org/downloads/release/python-3130a5/
https://github.com/python/cpython/releases/tag/v3.13.0a5

まだ開発途中のアルファ版ですが、今回はこのバージョンを使って検証していきます。

なお筆者の動作環境は以下の通りです。

  • CPU: AMD Ryzen 7 3700X(8コア)
  • OS: Ubuntu 22.04 (on WSL2 / Windows10)
    • gcc: 11.4.0

また、本記事の手順ではソースコードをビルドするためのツール群が必要になります。
お使いの環境に応じて必要な準備をしてください。

参考: Python Developer's Guide - Setup and building

ビルド/インストール手順

GILを無効化するにはビルド時にオプションを指定しておく必要があります。

オプションは--disable-gilとのこと。分かりやすいですね。
https://github.com/python/cpython/blob/076d169ebbe59f7035eaa28d33d517bcb375f342/configure#L1815-L1816

一連のコマンド手順は以下のようになります。

$ pwd
/home/skokado/playground-py313

$ # インストール用ディレクトリを作成
$ mkdir -p local/python-3.13

$ # ソースコードを取得
$ wget https://www.python.org/ftp/python/3.13.0/Python-3.13.0a5.tgz
$ tar xf Python-3.13.0a5.tgz
$ cd Python-3.13.0a5/

$ # オプションの確認
$ ./configure --help | grep gil
  --disable-gil           enable experimental support for running without the

$ # ビルド、インストール
$ ./configure --disable-gil --prefix $(pwd)/../local/python-3.13 && make install
checking build system type... x86_64-pc-linux-gnu
checking host system type... x86_64-pc-linux-gnu
checking for Python interpreter freezing... ./_bootstrap_python
...
()
Successfully installed pip-24.0

$ # インストールできたことを確認
$ cd ../local/python-3.13
$ ./bin/python3.13 -VV
Python 3.13.0a5 (main, Mar 14 2024, 18:37:25) [GCC 11.4.0]

ベンチマーク検証

GILはCPUバウンドなマルチスレッド処理において実行可能なスレッドが制限されるものです。
したがってマルチスレッド処理を行うスクリプトで実行結果を比較してみます。

検証スクリプトは以下です。

# test_gil.py

from concurrent.futures import ThreadPoolExecutor
import time
import math


def get_primes(max: int) -> list[int]:
  # (あえて低速な) n以下の素数一覧を返す関数
  if max < 2:
    raise ValueError()

  primes = [2]
  for n in range(3, max + 1):
    is_prime = True
    for i in range(2, int(math.sqrt(n)) + 1):
      if n % i == 0:
        is_prime = False
        break

    if is_prime:
      primes.append(n)

  return primes


if __name__ == "__main__":
  print("concurrency,time")
  for concurrency in range(1, 10 + 1):

    start = time.monotonic()

    with ThreadPoolExecutor(max_workers=concurrency) as executor:
      futures = [executor.submit(get_primes, 500000) for _ in range(concurrency)]

    for f in futures:
      f.result()

    end = time.monotonic()
    duration = end - start

    print(f"{concurrency},{duration:.2f}")
  • 「CPUバウンド処理」として素数判定をメインにした関数をThreadPoolExecutorでマルチスレッド処理する
    • ※引数maxは筆者の環境で1、2秒程度かかる値を選択
  • concurrencyで指定されたスレッド数分だけ get_primes を並列に起動する
  • concurrencyを1から10まで変化させて所要時間を計測する
    • ※それぞれ3回ずつ実行し、平均時間を取得

ちなみに、上記でビルドしたv3.13.0a5において実際の処理でGILを無効にするには環境変数PYTHON_GIL=0とともに実行する必要があります。

$ PYTHON_GIL=0 ./bin/python3.13 test_gil.py

結果

比較対象は以下の通りです。

  1. v3.12.2: 執筆時点の最新リリースバージョン
  2. v3.13.0a5: "--disable-gil" オプション無しでビルドしたランタイム
  3. v3.13.0a5 & --disable-gil: "--disable-gil" オプション付きでビルドかつ "PYTHON_GIL=0" 無しで実行
  4. v3.13.0a5 & --disable-gil & PYTHON_GIL=0: GILを無効化して実行

(単位: 秒)

cocurrency v.3.12.2 v3.13.0a5 v3.13.0a5 & --disable-gil v3.13.0a5 & --disable-gil & PYTHON_GIL=0
1 1.12 1.01 1.47 1.46
2 2.29 2.07 3.00 1.56
3 3.46 3.13 4.56 1.74
4 4.62 4.17 6.02 1.78
5 5.74 5.22 7.37 1.98
6 6.91 6.35 8.82 2.06
7 8.08 7.40 10.29 2.23
8 9.22 8.42 11.84 2.40
9 10.38 9.47 13.26 2.56
10 11.53 10.52 14.80 2.71

GIL無効化(--disable-gilオプションでビルトかつPYTHON_GIL=0)の場合のみ所要時間が並列度に単純比例せず、期待した結果となりました。

また、GILが有効なv3.12.2v3.13.0a5では単純に約10%程度高速になりました。
バージョンアップに伴って性能が改善されるのは嬉しいですね。

一方非マルチスレッド処理(concurrency=1)においては性能が悪化しました。
上記の検証スクリプトの場合、データ構造の安全性に関するオーバーヘッドの影響が考えられます。

コレクション - python.jp

PythonでGILの排除が難しい理由として、参照カウントの存在に加えて、Pythonインタープリタが辞書やリストなどの複雑なコレクションに依存している、という点もよく挙げられます。

残念ながら、手放しに喜べる検証結果とはなりませんでした。
今後のベータ版やrc版でも引き続き検証してみたいです。

まとめ

Python3.13の開発バージョンを用いてのGILの解消を確認しました。

プロセスあたりのマルチスレッド処理の性能向上が期待できますね。

GILの解消を提案したPEP 703によるとターゲットバージョンはPython3.13であり、順調に開発が進めば2024年10月にリリースされることになりそうです。

参考