「Python 3.13」で追加された新しいREPL、フリースレッドモードとは?:Python最新情報キャッチアップ
Python 3.13の新機能の中からこれまでよりも高機能なREPL、マルチスレッドプログラムを実行する際の足かせになることがあったグローバルインタープリタロックを無効化したフリースレッドモードについて見てみよう。
2024年10月にPython 3.13.0がリリースされた。今回から数回に分けて、Python 3.13で追加された新機能などを紹介していく。その中でも今回は、大きく改善された対話型インタープリタとグローバルインタープリタロックを無効化したフリースレッドモード(実験的サポート)について見てみる。
Python 3.13における大きな変更点
「What's New In Python 3.13」によれば、Python 3.13における大きな変更点としては次のものが挙げられている。
- 新しいREPL(対話型インタープリタ)
- フリースレッドモードでの動作の実験的なサポート
- JITコンパイラ
- エラーメッセージの改善
- locals関数が返送するマッピングオブジェクトの値を変更した際のセマンティクスの定義
- 型パラメーターのデフォルト値サポート
- 標準ライブラリの変更(改善、廃止されたAPIやモジュールの廃止など)
- iOS、Androidなどが公式にサポートされるプラットフォームとなった
これらの中から、以下では新しいREPLとフリースレッドモードでの動作の実験的なサポートを紹介する。
新しいREPL(対話型インタープリタ)
Python 3.13ではREPLが新しくなった。
新しい機能としては、以下のようなものが挙げられている。
- 複数行のコードの編集が可能になった(履歴も保持される)
- REPL固有のコマンドのサポート(help、exit、quitなど)
- プロンプトやトレースバックにデフォルトで色が使われるようになった
- [F1]キーによるヘルプ画面の表示
- [F2]キーによる入力履歴の表示
- [F3]キーによる貼り付けモードへの移行(行数の多いコードブロックを履歴からコピー&ペースト可能)
上の画像は[F1]キーを押下することでヘルプ画面を表示したところだ。この画面に移行するには、以前は「help()」とかっこ付きで入力する必要があったが、>>>プロンプトで[F1]キーを押すか、単に「help」と入力するだけでもよい(かっこが必要なくなった)。かっこが必要ないのはREPLを終了する際も同様で、以前は「quit()」と入力する必要があったのが、新しいREPLでは「quit」と入力するだけでよくなっている([Ctrl]+[Z]キーや[Ctrl]+[D]キーで終了させている人の方が多いだろうが)。
また、以下の画像を見ると分かるが、「>>>」や「...」などのプロンプトが色付きで表示されるようになった。
複数行のコードの編集はREPLの使い勝手を大きく改善してくれるだろう。以下はwith文でファイルをオープンして、その内容を行ごとに読み込もうとしているコードを入力しているところだ。
しかし、上のコードでは「as f」を書き忘れている。以前であれば、「あぁぁ」と入力ミスをした自分に悔やむところだが、新しいREPLではカーソルキーを使って直したい場所にカーソルを移動してコードを修正できる。
その後は入力中のコードの最後までカーソルを移動して[Enter]キーを押してから、空の「...」プロンプトに対してもう一度[Enter]キーを押せばコードを実行できる。
仮に修正しないままコードを実行してしまって例外が発生しても、今度は[↑]キーを押すだけで、複数行のコードの入力履歴が表示され、編集可能な状態になるので、カーソルを移動させてコードをすぐに修正できる。
[F2]キーを押すと、「>>>」と「...」という2つのプロンプト抜きでこれまでに入力したコードが表示される。REPLで以前に入力したコードをコピー&ペーストして使いたいけれど、プロンプトが邪魔だなぁと思った経験はないだろうか。そういうときにこれが使えそうだ。
ただし、貼り付けたい部分をコピーして「>>>」プロンプトにペーストしても次のようにインデントが崩れてしまうことがある(ここでは修正後のwith文以降をコピー&ペーストしている)。新しいREPLではインデントの自動的な挿入もある程度はやってくれるのだが、それが悪い方向に影響しているようだ(インデントがうまく調整されるかは環境によっても変わるかもしれない。時間がなくて検証できていないので、今後、時間があれば確認したいところ)。
複数行編集が可能なので、カーソルを移動して、手作業でインデントをそろえてもよいのだが、こういうときに使えるのが[F3]キーで移行するペーストモードだ。[F3]キーを押すとプロンプトが「(paste)」に変わり、そこにコピーしたコードを貼り付ければよい(もう一度、[F3]キーを押すとプロンプトは通常の「>>>」または「...」に戻る)。
コードを貼り付けたら、[Enter]キーで改行して、[F3]キーでプロンプトを元に戻してから、再度[Enter]キーを押せば、貼り付けたコードが実行される。
複数行のコードの編集が可能になり、以前に入力したコードの再入力も簡略化されたことでREPLでの簡単なコードの記述やお試しがはかどるようになるだろう。
フリースレッドモードでのコード実行の実験的なサポート
Python(CPython)では長らくグローバルインタープリタロック(GIL)と呼ばれる機構が採用されている。これはPythonのコードを実行するスレッドが一度に1つだけであることを保証する機構であり、スレッド安全性を高めてくれるが、その一方でマルチスレッドによるPythonコードの同時実行を妨げる原因ともなっている。
Python 3.13ではGILを無効化し、「フリースレッドモード」(free threaded mode)でのPythonコードの実行を実験的にサポートしたビルドが登場した。ただし、Python 3.13とは異なるバイナリとして配布されている(python3.13t、python3.13t.exe)。Pythonインストーラーを普通に実行しただけでは、フリースレッドモードをサポートしたPython処理系はインストールされない。
macOS用のインストーラーを実行した場合は、インストールをカスタマイズして、[Free-threaded Python [experimental]]チェックボックスをオンにする。
Windowsでも同様に、インストーラーでフリースレッドモードをサポートしたバイナリのダウンロードを指示する(管理者として実行する必要があるかもしれない)。
そしてインストールを進めていけば、フリースレッドモードをサポートしたPython 3.13tのバイナリもインストールされる。macOSでは「python3.13t」コマンドで、python.orgで配布されているWindows用のPythonでは「py -3.13t」のようにすればGILが無効化された(フリースレッドモードの)Python処理系が起動される。
ここではフリースレッドモードでのPythonコードの動作を確認するために、Python 3.12環境とPython 3.13t環境で以下のコードを実行してみることにした。
from threading import Thread
import time
def fib(n): # CPU負荷の高い計算をしてみる
if n == 0 or n == 1:
return 1
return fib(n-1) + fib(n-2)
def calc_fib(n):
threads = []
start_time = time.time() # 開始時刻を記録
for i in range(4): # スレッドは4つとする
threads.append(Thread(target=fib, args=(n,)))
threads[-1].start()
print(f'thread {i} started')
for t in threads:
t.join()
end_time = time.time() # 終了時刻を記録
print(f'Execution time: {end_time - start_time}')
print('done.')
calc_fib(40)
スリープ(time.sleep関数)のような処理ではCPUに対する負荷が高まらないので、ここではフィボナッチ数を計算する関数を定義して、その関数を4つのスレッドで実行することにした。コード自体はthreadingモジュールを使用するごく普通のコードといえるだろう(ただし、フィボナッチ数の計算結果の取得などについては何も考慮していないので、これは本当にCPUに負荷がかかる計算をマルチスレッドで行うだけのものだと考えてほしい)。
以下はmacOS上で動作するPython 3.12でこのコードを実行しているところだ。
Visual Studio Codeのウィンドウの上に浮いている4つのメーターがCPU使用率を表している。筆者が使用しているMac miniは第8世代のCore i3(4コア4スレッド)を搭載している(そのため、上の画像ではメーターが4つある)。メモリは16GBだが、その半分を同時に動いているWindows 10仮想マシンに割り当てている。貧相なマシンといえば貧相なマシンだ。それはともかくとして、Python 3.12ではGILが有効であり、Pythonコードを一度に実行できるのはスレッドが何個あろうと1つのスレッドだけであり、CPU使用率は上がらない傾向にあるといえるだろう。
Windows 10仮想マシンで同じコードをPython 3.12で実行したときも同じ傾向になった。
一方、フリースレッドモードをサポートしているPython 3.13tで上のコードを実行しているところが以下だ(macOS)。
これはWindows 10仮想マシンでも同様だ(2コア2スレッドが仮想マシンには割り当てられている)。
GILがもたらす制約から解放され、CPUを最大限活用していることが分かる。なお、大まかな実行時間については次のようになっている。
環境 | 実行時間 |
---|---|
Python 3.12(macOS) | 85秒程度 |
Python 3.12(Windows 10) | 100秒程度 |
Python 3.13t(macOS) | 40秒程度 |
Python 3.13t(Windows 10) | 80秒程度 |
環境ごとの実行時間 |
Windows 10仮想マシンには2コア(2スレッド)が割り当てられている。フリースレッドモードでは各スレッドが全開でCPUを使い倒せることを考えると、スレッド数が倍のmacOSがWindows 10よりもおおよそ倍の速度でコードを実行できていることは納得できそうだ。これに対して、GILが有効なPython 3.12ではそもそもがそれほどCPUを使えていないことから、スレッド数の差ほど実行速度に差が出なかったのかもしれない。
フリースレッドモードが有効であれば、CPUに高い負荷がかかるような処理であれば、それなりの速度向上が見込めるだろう。その一方で、リソースなどへのアクセスがスレッド間で競合する可能性もある。これらをうまく制御する必要も出てくるだろう。
まだまだ実験的な段階だが、Pythonコードの実行を高速化するという意味ではこれからも注目が必要な機能といえる。
Copyright© Digital Advantage Corp. All Rights Reserved.