1 ポイント 投稿者 GN⁺ 2023-07-30 | 1件のコメント | WhatsAppで共有
  • 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 APIPython 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件のコメント

 
GN⁺ 2023-07-30
Hacker Newsのコメント
  • 何十年もの間、多くのCライブラリのコードには、非同期、再入、再帰のコンテキストでは不安定だというマニュアル上の警告が付いていた。
    それでも対処法を学び、APIの不安定性を大きく増やさない形で、再入安全なバージョンが段階的に配布されていった。
    その場でトークン化する文字列解析、静的バッファを使うDNS呼び出し、Vax特有のスタック動作に依存するコードなどがあり、GILは祝福であると同時に呪いでもあったと思う。

    • 再入不可な関数を探すためにCランタイムのドキュメントを隅々まで調べていた記憶がある。
      そのおかげで、ある程度知っているAPIでも、重要な細部を見落としていないか、変更されていないかを確認するためにドキュメントを見る習慣がついたように思う。
      その頃クロスプラットフォームC++をやっていて初めて見たJavaは、並行性、ガベージコレクション、C++より書きやすいさまざまな機能を最初から備えており、大きく伸びると確信した。
      その後、主流の開発者たちがPythonを使い始めたが、記憶では、もともと組み込み可能な拡張言語として単純だったので、GILもより筋が通っていた。
    • No-GILモードは任意で、ライブラリは「no-GIL互換」と表示され、エコシステムは段階的により多く対応していくことになる。
      誰かがスイッチを入れて、膨大な怪しいCコードを一度に壊すわけではない。
    • その状態が永遠に続くことはあり得ない。
      今では128コアCPUまで登場し、安価なCPUでも6コアを備える時代なので、単一コア性能に縛られる制約は時間とともにさらに大きくなる。
    • 90年代にCを本格的に始めたときにも、こうした警告を見た。
      最初は数週間、長くても数か月で解決する問題だと信じていたが、あまりに何も分かっていなかったということだ。
    • ここで同等性を見るのは難しい。
      Cではスレッドセーフでない関数との相互作用がはるかに直接的で、Cを使うときはたいていもっと慎重になる。
      Pythonにはグローバル状態を持つCモジュールが丸ごと存在し、10個ほどロードしたうえでインタプリタの複雑さまで加わると、すぐに誰も何が起きているのか分からなくなる。
      今でもほとんどの開発者、さらにはコア開発者でさえメモリリークを確認しておらず、tsanを走らせるとは思えないし、走らせたとしてもコードの10%しかカバーしない小さなテスト群である可能性が高い。
      Python、とくにAI分野のソフトウェア開発慣行を見ると、この機能についてはかなり悲観的だ。
  • 興味深くはある。
    Pythonは、大半がグローバルロックに依存してよいと理解されて書かれたC共有ライブラリで構成されている。
    十分に単純でロックなしでもうまく動くものもあるが、他のものは依然としてロックが必要で、今後はGILなしで実行せよという圧力を受けることになる。
    その一部は自分の範囲内で直接ロックを実装するだろうが、Pythonに本当に足りなかったものは、エコシステムのあちこちに散らばる場当たり的なミューテックス呼び出しだったのかもしれない。
    性能を口実にデータ競合とデッドロックが導入される形でPythonが壊れていくとは予想していなかった。
    グローバルロックを前提に書かれたCライブラリをスレッドセーフにする作業は、並行性の専門家でも止めるようなもので、実装中にミスをしやすい種類の仕事だ。
    Python C拡張を書いた人の大半は並行性の専門家ではなく、挑戦を避けない優れたプログラマーたちだというのが私の仮説で、この組み合わせならデータ競合/停止/セグフォルトはほぼ必然に見える。

    • 最初の段階として明示的なオプトアウトにするのはよさそうだ。
      だが5年後にオプトインへ変えるという期待は楽観的すぎる。
      すべてのライブラリ開発者が自分のライブラリだけでなくPythonライブラリまで直さなければならず、大変な作業で、うまくやっても誰にも気づかれず報われにくい。
      マルチプロセスのユースケースがまったくなかったライブラリも多く、大きなライブラリでは微妙なバグが生じるのは避けられないため、再現しない苦情と開発者の諦めが約束された結果のように見える。
    • Pythonから頻繁に呼ばれるCライブラリの多くは他の言語向けバインディングもあるので、今ごろはスレッドセーフであるべきではないかと思う。
      GILのあるPythonもスレッドをサポートしているため、こうしたライブラリは少なくとも再入安全ではあるはずだ。
      再入は可能だがスレッドセーフではないライブラリなら、すべての呼び出しを包むグローバルロックを1つ追加すれば十分かもしれず、これはGILがしていたこととかなり似ている。
      既存ライブラリをGILなしで動作させることは、多くの場合かなり素直に進められそうで、並列性は犠牲になるかもしれない。
      核心的な問題は、C側からPythonランタイムへコールバックするライブラリだと思う。
  • 素朴な疑問だが、asynciomultiprocessing パッケージがあるのに、誰が 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 は実用的
    • No-GIL が multiprocessing では解決できないケースは多い
      共有状態を使って複数のクライアントに同時に応答する Web サーバーがあり、multiprocessing はデータをやり取りする際に pickle を使うため性能オーバーヘッドが大きい
      例えばメモリ上に 1GB のデータ構造を置いて並列計算したい場合、multiprocessing で高性能に処理するのは難しい
      pickle を使うとすべてのオブジェクトを共有できるわけではなく、pickle できないオブジェクトのエラーは複雑なデータ構造ではデバッグが非常に難しい
      特にネイティブライブラリが作ったオブジェクトは共有できないことがある
      実行中の状態を共有しなければならない処理も multiprocessing モジュールでは非常に難しく、Flask 用の Prometheus exporter でさえ全プロセスの統計を集めるには一時ディレクトリを使う奇妙なハックが必要になる
    • PEP 703 では、DeepMind の強化学習チームの Manuel Kroiss が、GIL のボトルネックのために Python コードベースを C++ で書き直すことになり、研究者がアクセスしにくくなると説明している
      DeepMind の多くのアプリケーションでは、プロセスあたり 50〜100 個程度のスレッドを動かしたいが、10 個未満でも GIL がボトルネックになることが多いという
      回避策としてサブプロセスを使うこともあるが、多くの場合プロセス間通信のオーバーヘッドが大きくなりすぎ、結局 Python コードベースの大部分を C++ に移すことになるという
      Web アプリのような一般的な用途では multiprocessing で十分かもしれないが、Google や DeepMind のような大規模 AI ワークロードでは、GIL が実際に Python の利用を制限している
      Meta がこの作業にエンジニア 3 年分を投入しようとしている理由もここにある: https://news.ycombinator.com/item?id=36643670
    • CPU バウンドな問題では、イベントループは依然として単一コアでしか動かないため、asyncio はまったく役に立たない
      名前の通り、入出力バウンドな問題でだけ本当に役立つ
      複数プロセス間のデータ共有は途方もない苦痛であり、データ制御とプロセスのオーケストレーションを一緒に行うのはさらに大きな苦痛
      プロセスは高コストで、先に述べたデータ共有の難しさのために greenlet も実質的な代替にはなりにくい
    • 平均的な Web アプリケーションには、それほど革命的ではなさそう
      しかし 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 を使うことになるだろう
    • 問題ないと思う
      問題があると疑っている状態から、正確にどこに問題があるのか分かっている状態へ移るということで、あとはそのリストを一つずつ削っていけばよい
      コードの周辺に何らかの形のミューテックスを追加するか、問題が起きにくい非ネイティブコードの代替実装に置き換えればよい
      反対論は主に作業量が多いということであって、不可能だということではなさそう
      やる人が十分にいれば成果は出せる
    • 「極めてまれ」というのは、動き続けるサーバー数千台を支える並列/非同期ランタイムを仕事で扱っている立場からすると、実際には常に壊れるがデバッグは不可能、という意味
    • CPython のコア開発者たちはこうした問題を非常によく理解していると思う
      そうでなければ、人々が自分で No-GIL モードを選べるようにする段階的アプローチではなく、GIL を突然丸ごと取り除く計画を発表していたはず
    • 既存の拡張と競争できるような、並行性優先の C 拡張を書くにはよいタイミングかもしれない
  • テキストから Unicode への移行、32ビットから64ビットへの移行、Intel から ARM への移行、Y2Kを思い出せばよい
    No-GIL ははるかに小さな変化であり、過激に壊すことなく同じ移行経路をたどれるはず
    何か壊れるものがあったとしても、そうしたケースを扱うための明確に定義された方法があるはず
    私たちはいずれにせよそれらの移行を乗り越えてきたし、前に進む姿を見るのは喜ばしい
    これまで不可能とされていた、より多くの領域を開いてくれるだろう
    初期の Swift がうまくやったことの一つは、破壊的変更を約束の中に組み込んだ点で、誰もが自分の立ち位置を理解し、うまく適応した
    ときには Python も同じ道を選んでほしいと思う

    • それは少し違う
      32ビットから64ビットへ、ARM へ、Y2K はいずれも動作するかどうかをテストできた
      もちろんテストが失敗ケースを網羅できないことはあるが、実施したテストは決定的だった
      しかしここでは、いくらテストしても答えは「正しいか、まだ適切な 競合状態 に当たっていないか」のどちらかでしかない
    • マイグレーションの難しさを心配しているのではなく、最終状態が実際に今より悪くなる可能性を心配している
    • テキストから Unicode へ移行することは、Python にとって特に大きな問題だった
  • Python 3 移行シナリオを繰り返したくないと明言してはいるが、今のアプローチも不気味なほどその道に近く見える
    多くの部分は Python コミュニティと配布チャネルにかかっている
    コミュニティが適時に導入できないかもしれないし、Ubuntu、Fedora、Anaconda のようなディストリビューションが性急に先行するかもしれない
    断定するには早いが、このようなシナリオを避けるために Steering Council が実際にどれほど統制力を持っているのか疑問だ

    • No-GIL が提供されてから5年後には、それが唯一のビルドモードになることを望んでいると言っていたが、これは長すぎると同時に短すぎる
      Python が 2つのモード を持つには5年は長すぎる
      半世紀ならぬ半十年もあれば、2つのモードが現状維持として固定化するには十分で、その後も古い Stack Overflow の投稿は残り続けるだろう
      5年が10年の不確実性と破損に伸びないとは楽観していない
      逆に5年は、すべての C コードを引っ張り出して修正し、テストし、成熟したと呼ぶには短すぎるかもしれない
    • 2to3 の状況に似たものになるだろう
      支援を約束した企業が一部のプロジェクトを機械的に変換し、実際の開発者を悩ませたり、フォークで脅したりするかもしれない
      バグは実際の無償開発者たちが何年もかけて磨き込むことになるだろう
      しかし Python には何らかの「成功」が必要で、これは良い宣伝文句になる
      Python の世界では正確性はそれほど重要ではないようだ
    • 2-to-3 で、特に理由もなく壊れるメジャーバージョン移行カードを使ってしまい、今度は大きな変更を前にして「2-to-3 のようにはならない」と言っている
      その歴史のせいで、別のメジャー移行へ進むよりも、CPython 3 の中で2つの実行モードを維持しようとしているように聞こえる
  • これが容易に Python 4 の惨事 になり得ることを強く意識しているのは幸いだ
    yes-GIL の動作に偶発的な影響を与えないよう、極めて慎重でなければならない
    何らかの形でエミュレートされた GIL が実際の GIL と正確に同じでないなら、あらゆる奇妙なケースが起こり得る

    • 2 -> 3 と違うものになる理由として、これまで見たのは2つだけだ
      第一に、そうなってほしくないということ
      第二に、そうなったら早く諦めるということ
      どちらも重要だが、「そしてそれをこのように実現する」という重要な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 の使用例