- Python Steering Councilは、CPythonでGILをオプションにするPEP 703を受け入れる意向であり、no-GIL提案とPEP 703の双方に対するコミュニティの反応もおおむね好意的
- 長期目標はno-GILビルドへ一本化することであり、with-GILビルドとno-GILビルド、および拡張モジュールのエコシステムが恒久的に分断される状況は避けたいとしている
- 移行過程では後方互換性が重要な制約であり、no-GIL対応のために変更されたサードパーティコードもwith-GILビルドで引き続き動作する必要がある
- 計画は短期の実験ビルド、中期のサポート対象ビルド、長期のデフォルトビルドという3段階の移行であり、実験ビルドはPython 3.13に入る可能性があるが、3.14にずれ込んでも問題とは見ていない
- デフォルトビルドへ移行する前に、コミュニティのサポートとAPI・パッケージング・配布の経験を確認する必要があり、混乱が利益を上回るならPEP 703を中止して別の解決策を探すべきとしている
Steering CouncilによるPEP 703受け入れの方向性
- Python Steering CouncilはPEP 703を受け入れる意向がある
- no-GIL提案に関するアンケートでは全体的に好意的な反応があり、Steering Councilも一般的なアイデアとPEP 703の双方におおむね前向きである
- ただし受け入れの詳細はまだ作業中であり、今後数週間で最終化する予定
恒久的な分岐なしで進める移行原則
- 長期的には、おそらく5年以上の期間をかけてno-GILビルドが唯一のビルドになるべきである
- with-GILビルドとno-GILビルドが恒久的に分かれる状況は望んでいない
- 拡張モジュールのエコシステムも、2つのビルドに恒久的に分離される構造を避けたいとしている
- 後方互換性は非常に慎重に扱う必要がある
- 新たなPython 3移行のような状況は作りたくない
- no-GILビルド対応のためにサードパーティコードが変更されたとしても、その変更はwith-GILビルドでも動作する必要がある
- 以前のPythonバージョンとの後方互換性の問題は別途扱う必要がある
- これはPython 4ではない
- ABI互換性の要件、2つのビルドの詳細、後方互換性への影響はまだ検討中である
デフォルト移行前に確認すべき条件
- no-GILビルドをデフォルトに変更する前に、コミュニティのサポートが十分かどうかを確認する必要がある
- デフォルトだけを変更して、コミュニティに必要な作業を自力で見つけさせるわけにはいかない
- コア開発者は新しいビルドモードとその周辺要素を自ら体験する必要がある
- 既存コードのスレッド安全性を整理する中で、新しいC APIとPython APIが必要になる可能性がある
- その過程で得た知見をPythonコミュニティと共有する必要がある
- コア開発者が望む変更と、コミュニティに求める変更が受け入れ可能な水準かを確認する必要がある
- no-GILをデフォルトにするまでに、変化が大きすぎる混乱を生み、利益が小さいと判断された場合には方向転換できるようにすべきである
- この場合、すべての作業を取り消す決定も可能であるべき
- したがって、no-GIL専用コードはある程度識別可能である必要がある
3段階の移行計画
- 短期的には、no-GILビルドを実験的ビルドモードとして追加する
- おそらくPython 3.13に入る可能性がある
- Python 3.14にずれ込んでも問題とは見ていない
- 実験的という状態は、コア開発者がそのビルドモードを支持している一方で、コミュニティがすぐにサポートすると期待できないことを明確にするためのもの
- API設計、パッケージング、配布の面でコミュニティサポートを可能にするには時間が必要
- ディストリビューションが実験的なno-GILビルドをデフォルトインタープリタとして提供することは推奨されない
- 中期的には、no-GILビルドをサポート対象にするが、まだデフォルトにはしない
- 本番利用が可能なほどコミュニティサポートが十分であるという確信が必要
- この段階で、no-GILをデフォルトにする目標日またはPythonバージョンを定める
- 時期は、API変更の後方互換性、stable ABIの扱い、コミュニティが必要だと考える作業量に大きく左右される
- 最低でも1〜2年、またはそれ以上かかる可能性がある
- サポート対象と宣言されれば、一部の配布者はno-GILをデフォルトとして提供し始める可能性があるが、その時点でどれだけ多くのPythonパッケージがno-GILをサポートしているかによって変わりうる
- 長期的には、no-GILをデフォルトにし、GILの痕跡を取り除こうとしている
- 不必要に後方互換性を壊すことはしない
- 2つの一般的なビルドモードを長く維持すると、テストリソースやデバッグシナリオが倍増するなど、コミュニティの負担が大きくなりうる
- とはいえ急ぐこともできず、この段階に至るまで最大5年かかる可能性がある
継続的な再評価と中止の可能性
- 全体の過程で、Steering Councilだけでなくコア開発者も進行状況と提案されたスケジュールを継続的に再評価する必要がある
- この作業が新たな10年がかりの後方互換性をめぐる戦いになることは望んでいない
- PEP 703が問題になりそうな兆候が見えれば、中止して別の解決策を探せるようにすべきである
- 継続中の作業にそれだけの価値があるかを定期的に確認する必要がある
1件のコメント
Hacker Newsのコメント
何十年もの間、多くのCライブラリのコードには、非同期、再入、再帰のコンテキストでは不安定だというマニュアル上の警告が付いていた。
それでも対処法を学び、APIの不安定性を大きく増やさない形で、再入安全なバージョンが段階的に配布されていった。
その場でトークン化する文字列解析、静的バッファを使うDNS呼び出し、Vax特有のスタック動作に依存するコードなどがあり、GILは祝福であると同時に呪いでもあったと思う。
そのおかげで、ある程度知っているAPIでも、重要な細部を見落としていないか、変更されていないかを確認するためにドキュメントを見る習慣がついたように思う。
その頃クロスプラットフォームC++をやっていて初めて見たJavaは、並行性、ガベージコレクション、C++より書きやすいさまざまな機能を最初から備えており、大きく伸びると確信した。
その後、主流の開発者たちがPythonを使い始めたが、記憶では、もともと組み込み可能な拡張言語として単純だったので、GILもより筋が通っていた。
誰かがスイッチを入れて、膨大な怪しいCコードを一度に壊すわけではない。
今では128コアCPUまで登場し、安価なCPUでも6コアを備える時代なので、単一コア性能に縛られる制約は時間とともにさらに大きくなる。
最初は数週間、長くても数か月で解決する問題だと信じていたが、あまりに何も分かっていなかったということだ。
Cではスレッドセーフでない関数との相互作用がはるかに直接的で、Cを使うときはたいていもっと慎重になる。
Pythonにはグローバル状態を持つCモジュールが丸ごと存在し、10個ほどロードしたうえでインタプリタの複雑さまで加わると、すぐに誰も何が起きているのか分からなくなる。
今でもほとんどの開発者、さらにはコア開発者でさえメモリリークを確認しておらず、tsanを走らせるとは思えないし、走らせたとしてもコードの10%しかカバーしない小さなテスト群である可能性が高い。
Python、とくにAI分野のソフトウェア開発慣行を見ると、この機能についてはかなり悲観的だ。
興味深くはある。
Pythonは、大半がグローバルロックに依存してよいと理解されて書かれたC共有ライブラリで構成されている。
十分に単純でロックなしでもうまく動くものもあるが、他のものは依然としてロックが必要で、今後はGILなしで実行せよという圧力を受けることになる。
その一部は自分の範囲内で直接ロックを実装するだろうが、Pythonに本当に足りなかったものは、エコシステムのあちこちに散らばる場当たり的なミューテックス呼び出しだったのかもしれない。
性能を口実にデータ競合とデッドロックが導入される形でPythonが壊れていくとは予想していなかった。
グローバルロックを前提に書かれたCライブラリをスレッドセーフにする作業は、並行性の専門家でも止めるようなもので、実装中にミスをしやすい種類の仕事だ。
Python C拡張を書いた人の大半は並行性の専門家ではなく、挑戦を避けない優れたプログラマーたちだというのが私の仮説で、この組み合わせならデータ競合/停止/セグフォルトはほぼ必然に見える。
だが5年後にオプトインへ変えるという期待は楽観的すぎる。
すべてのライブラリ開発者が自分のライブラリだけでなくPythonライブラリまで直さなければならず、大変な作業で、うまくやっても誰にも気づかれず報われにくい。
マルチプロセスのユースケースがまったくなかったライブラリも多く、大きなライブラリでは微妙なバグが生じるのは避けられないため、再現しない苦情と開発者の諦めが約束された結果のように見える。
GILのあるPythonもスレッドをサポートしているため、こうしたライブラリは少なくとも再入安全ではあるはずだ。
再入は可能だがスレッドセーフではないライブラリなら、すべての呼び出しを包むグローバルロックを1つ追加すれば十分かもしれず、これはGILがしていたこととかなり似ている。
既存ライブラリをGILなしで動作させることは、多くの場合かなり素直に進められそうで、並列性は犠牲になるかもしれない。
核心的な問題は、C側からPythonランタイムへコールバックするライブラリだと思う。
素朴な疑問だが、asyncio と multiprocessing パッケージがあるのに、誰が No-GIL を必要とするのだろうと思う
Python で GIL のせいで問題に遭遇したことはなく、ThreadPool や ProcessPool を立ち上げたり、必要なら非同期ライブラリを使ったりして、常に回避してきた
multiprocessing では解決できない No-GIL のユースケースがあるのか気になる
並行性プリミティブのオーバーヘッドがない単一スレッド実行が、高性能コンピューティングには最適だと思っていた。LMAX Disruptor が示したように
asyncio は本質的に単一スレッドなので単一コアであり、multiprocessing はマルチコアなので性能面では有利だが、各プロセスが比較的重く、共有メモリのオーバーヘッドも追加される
GIL ベースのマルチスレッドは単一コアで、しかも正しく使うのが難しい
No-GIL のマルチスレッドはマルチコアだが使うのは難しく、実装はよく分からないものの、共有メモリは multiprocessing より速いはず
新しいシステムを設計するなら、ほぼすべての Python ユースケースでスレッドには触れず asyncio/multiprocessing を使う、という点には同意する
高速なマルチスレッドが必要な Python プログラムは、そもそも Python で書くべきではなかったケースが多いが、すでに CPU 集約的なコードを Python で書いている人たちがいるので、No-GIL は実用的
共有状態を使って複数のクライアントに同時に応答する Web サーバーがあり、multiprocessing はデータをやり取りする際に pickle を使うため性能オーバーヘッドが大きい
例えばメモリ上に 1GB のデータ構造を置いて並列計算したい場合、multiprocessing で高性能に処理するのは難しい
pickle を使うとすべてのオブジェクトを共有できるわけではなく、pickle できないオブジェクトのエラーは複雑なデータ構造ではデバッグが非常に難しい
特にネイティブライブラリが作ったオブジェクトは共有できないことがある
実行中の状態を共有しなければならない処理も multiprocessing モジュールでは非常に難しく、Flask 用の Prometheus exporter でさえ全プロセスの統計を集めるには一時ディレクトリを使う奇妙なハックが必要になる
DeepMind の多くのアプリケーションでは、プロセスあたり 50〜100 個程度のスレッドを動かしたいが、10 個未満でも GIL がボトルネックになることが多いという
回避策としてサブプロセスを使うこともあるが、多くの場合プロセス間通信のオーバーヘッドが大きくなりすぎ、結局 Python コードベースの大部分を C++ に移すことになるという
Web アプリのような一般的な用途では multiprocessing で十分かもしれないが、Google や DeepMind のような大規模 AI ワークロードでは、GIL が実際に Python の利用を制限している
Meta がこの作業にエンジニア 3 年分を投入しようとしている理由もここにある: https://news.ycombinator.com/item?id=36643670
名前の通り、入出力バウンドな問題でだけ本当に役立つ
複数プロセス間のデータ共有は途方もない苦痛であり、データ制御とプロセスのオーケストレーションを一緒に行うのはさらに大きな苦痛
プロセスは高コストで、先に述べたデータ共有の難しさのために greenlet も実質的な代替にはなりにくい
しかし Python が大きな比重を占める AI や データサイエンス のような領域では、CPU/GPU バウンドなスレッドを大量に立ち上げて動かせることは大きな利点
多くの C 拡張はマルチスレッドを念頭に置かずに書かれているため、問題が起きる可能性があり、実際そうなりそう
lstが別のスレッドからアクセスされ得るなら安全ではない小さな例はここにある: https://news.ycombinator.com/item?id=36649769今でも C コードが
__del__メソッド経由で Python バイトコードにコールバックし、そのバイトコードが十分長ければ、おそらく 100 命令程度でコンテキストスイッチが起こり得るただし極めてまれなケースであり、多くの C 拡張コードはこうした状況を考慮して書かれていない
C 拡張を使う人たちは、それらが原子的に実行されると依存しているかもしれない
例えばスレッドプールで numpy 配列に入れて取り出すようなやり方は、今はうまく動くが、GIL がなければ壊れる可能性がある
だから提案と作業方針は、non-GIL モードを完全にオプションのままにし、デフォルトにはしない方向になっている
これを有効にして使う勇敢な少数派は、何十年も前からある Python ライブラリコードの微妙な競合状態を見つけて直すために、膨大な時間を費やす覚悟が必要
初期導入者は多くの苦痛を味わうか、より可能性が高いのは、依存関係をできるだけ減らした非常に特化した専用プロセスに限って non-GIL を使うことになるだろう
問題があると疑っている状態から、正確にどこに問題があるのか分かっている状態へ移るということで、あとはそのリストを一つずつ削っていけばよい
コードの周辺に何らかの形のミューテックスを追加するか、問題が起きにくい非ネイティブコードの代替実装に置き換えればよい
反対論は主に作業量が多いということであって、不可能だということではなさそう
やる人が十分にいれば成果は出せる
そうでなければ、人々が自分で No-GIL モードを選べるようにする段階的アプローチではなく、GIL を突然丸ごと取り除く計画を発表していたはず
テキストから Unicode への移行、32ビットから64ビットへの移行、Intel から ARM への移行、Y2Kを思い出せばよい
No-GIL ははるかに小さな変化であり、過激に壊すことなく同じ移行経路をたどれるはず
何か壊れるものがあったとしても、そうしたケースを扱うための明確に定義された方法があるはず
私たちはいずれにせよそれらの移行を乗り越えてきたし、前に進む姿を見るのは喜ばしい
これまで不可能とされていた、より多くの領域を開いてくれるだろう
初期の Swift がうまくやったことの一つは、破壊的変更を約束の中に組み込んだ点で、誰もが自分の立ち位置を理解し、うまく適応した
ときには Python も同じ道を選んでほしいと思う
32ビットから64ビットへ、ARM へ、Y2K はいずれも動作するかどうかをテストできた
もちろんテストが失敗ケースを網羅できないことはあるが、実施したテストは決定的だった
しかしここでは、いくらテストしても答えは「正しいか、まだ適切な 競合状態 に当たっていないか」のどちらかでしかない
Python 3 移行シナリオを繰り返したくないと明言してはいるが、今のアプローチも不気味なほどその道に近く見える
多くの部分は Python コミュニティと配布チャネルにかかっている
コミュニティが適時に導入できないかもしれないし、Ubuntu、Fedora、Anaconda のようなディストリビューションが性急に先行するかもしれない
断定するには早いが、このようなシナリオを避けるために Steering Council が実際にどれほど統制力を持っているのか疑問だ
Python が 2つのモード を持つには5年は長すぎる
半世紀ならぬ半十年もあれば、2つのモードが現状維持として固定化するには十分で、その後も古い Stack Overflow の投稿は残り続けるだろう
5年が10年の不確実性と破損に伸びないとは楽観していない
逆に5年は、すべての C コードを引っ張り出して修正し、テストし、成熟したと呼ぶには短すぎるかもしれない
支援を約束した企業が一部のプロジェクトを機械的に変換し、実際の開発者を悩ませたり、フォークで脅したりするかもしれない
バグは実際の無償開発者たちが何年もかけて磨き込むことになるだろう
しかし Python には何らかの「成功」が必要で、これは良い宣伝文句になる
Python の世界では正確性はそれほど重要ではないようだ
その歴史のせいで、別のメジャー移行へ進むよりも、CPython 3 の中で2つの実行モードを維持しようとしているように聞こえる
これが容易に Python 4 の惨事 になり得ることを強く意識しているのは幸いだ
yes-GIL の動作に偶発的な影響を与えないよう、極めて慎重でなければならない
何らかの形でエミュレートされた GIL が実際の GIL と正確に同じでないなら、あらゆる奇妙なケースが起こり得る
第一に、そうなってほしくないということ
第二に、そうなったら早く諦めるということ
どちらも重要だが、「そしてそれをこのように実現する」という重要な3つ目のピースが欠けているように思える
すでに GIL と No-GIL が5年以上共存し得ると言っている
ツール制作者にとっては、少なくとも今後5年間、コストが2倍になるという意味だ
本番用途であれ実験用途であれ、人々は両方のモードでツールを使いたがるだろうからだ
GIL は Global Interpreter Lock のこと
良い説明はここにある: https://realpython.com/python-gil/
ここには大きな問題が2つある
第一に、ある種の改善は後方互換性を壊す価値があり、GIL の削除 はそうした改善だ
Python 3 の変更がそれに値したかどうかは議論の余地があり、
printが関数になったことは特に価値があるようには見えないただし 2-to-3 移行は声の大きい少数派が誇張した面があり、5つ以上のコードベースを2から3へ移した経験では、ほとんどは問題が少なかった
最大の問題は、以前の開発者たちがあらゆるものにライブラリを引き込んでしまい、放置されたライブラリの塊になっていたコードベースであり、こうしたコードは中核言語が互換性を壊さなくても問題に直面する
答えは、言語に永遠に互換性を壊せないよう圧力をかけることではなく、pip 全体を持ち込んでも持続可能な戦略になると期待しないことだ
現在の Steering Council は、声の大きい少数派からあまりに多くの非難を受け、破壊的変更を恐れている状態だ
しかし GIL の削除は Python の動作方式のあまりに根本的な部分なので、破壊的変更であるべきであり、それを認めて移行計画を立てるほうがよい。ユーザーを恐れて事実を認められないまま、壊れないようにするという不可能なことを試みるのは悪い考えだ
Python 3.11 でも私のコードベースは壊れ、修正は難しくなかったが、こうしたことが起こり得るというコミュニケーションがもっと良ければと思った
第二に、Python の他の多くの機能が GIL を中心に作られていることのほうが、より根本的な問題だ
特に 非同期パラダイム は GIL があるからこそ大きな意味を持つが、GIL がないなら、振り返ってみれば Erlang 式の send/recv アクターモデルのほうがはるかに良い方向だったはずだ
これを巻き戻すのは難しく、Python が互いにうまく噛み合わない機能群の、まとまりの弱い集合へ押しやられているように感じられ、あまりに遅すぎ、あまりに少なすぎるように見える
Python のコア開発者と Steering Council に感謝します。Python は Java や C と並んで好きな言語の一つです
Python の真のマルチスレッディングを大いに歓迎します
プロジェクトによって multiprocessing と multithreading の両方を使っており、multiprocessing の例は [0] に、入出力の多い処理に Python Threads を使った例は [1] にあります
しかし、本物のスレッドを使うほうがはるかに効率的なはずです
スレッドは任意の量のデータを単一のアトミックでほぼ即時の操作としてやり取りできますが、ローカルのループバックインターフェースや multiprocessing、パイプではそれはできません
「three tier multithreading architecture」と呼ぶマルチスレッディングアーキテクチャに取り組んでいます
https://github.com/samsquire/three-tier-multithreaded-archit...
目標は極めてスケーラブルで高性能なサーバーですが、Python はおそらくその作業に適した道具ではないかもしれません
[0]: https://news.ycombinator.com/item?id=36897054 multiprocessing の使用説明
[1]: https://devops-pipeline.com/ multithreading の使用例