PEP 703

https://peps.python.org/pep-0703/

Python の GIL 外す話。これすごく楽しい読みものでした。参照カウントのところが一番人気だと思うのですが、他のところも色々良い。こういう、「んーこういうことするとこういう問題が起きない?」と思ったら次の章くらいでそれが説明される、みたいな読みものは大変好きです

  • 参照カウント: オブジェクトっていうのは作ったスレッドが解放するというのがほとんどなんだから、その場合はロックをいらなくする、他に渡ったら普通の参照カウントぽくする、という話。 Swift に 2018 年に導入された 話らしい。他のスレッドに渡された後で DECREF すると他スレッド用の参照カウントが負になりうるのだけど、その時に queue に入れるということをして、ややこしいので、なんかこれ無しですむ方法はないのかなぁ……と
  • Immortalize と Deferred reference count: ずっと解放されないオブジェクトは不死にして contention を避ける。ほとんどの場合 cyclic になることが知られているオブジェクト(トップレベルのモジュールや関数など)も、 refcount やめて GC 任せにすることによって contention を避ける
  • mimalloc: pymalloc は GIL 前提でスレッドセーフにするの面倒なので、 mimalloc に変更。 mimalloc の内部状態を見ることで、 python が自力管理してた object のリストとかの管理はいらなくなった。後で他のメリットも出てくる
  • GC: Stop the world する。途中でデストラクタとか呼ぶとデッドロックしうるので後でやる、とか、まぁそうよね的な。 concurrent は writer barrier 入れられないから諦め。 generational は refcount あるしいらないもん!という感じで撤去、らしい
  • コンテナ: GIL 任せにしてたところをオブジェクト単位のロックにする。 list_a.extend(list.b) みたいな、複数のオブジェクトがあるとロック順序問題にならない?と思ったら、二つの mutex を受けて、 mutex のアドレス順でロックする、て関数をみんなが呼べば大丈夫、という話らしい。すごく単純だし、他でもよくあるのだろうけど、なるほどというアイデア
  • 楽観ロック: list と dict は大事なので、 getter は楽観ロックで高速化する。値取り出した後、もう一回変わってないかチェックするようなやつ。 rwlock は遅いから RCU にせよとかいうおきまりの話
  • 楽観ロックのための mimalloc 改変1: python オブジェクトは専用のプールを使う。それによって、オブジェクト取った後に仮に他スレッドによって解放されたとしても、 refcount は refcount のまま。なぜなら PyObject の先頭の方のレイアウトは共通だから (実際は二種類あるらしい)
  • 楽観ロックのための mimalloc 改変2: とはいえ munmap されたり、他のサイズ用のプールとして再利用されてしまうと困る。次の GC まで munmap しない、が安全だがメモリ使用量が増える。ので、ほど良い sync 頻度で安全なタイミングを保証する方法を実装する。グローバルな write カウンタと、各スレッドに read カウンタを用意する。空っぽのプールができたら、そのプールの番号を今の write カウンタとし、 write カウンタをインクリメント。各スレッドは楽観ロック中でない時に、時々 write カウンタを観測して自分の read カウンタに入れる。すると、全 read カウンタの最小の値より大きい番号を持つ空プールは捨てて良い、となる
なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h