Python3.13からのPyObjectのメンバーを知る会

## 前書き
この記事は[JSL(日本システム技研) Advent Calendar 2024](https://qiita.com/advent-calendar/2021/jsl)の記事です。(ちなみに前回自分が投稿したアドカレは2021年...:stuck_out_tongue_winking_eye:)

## 忙しい人向け
**今まで**
* ob_refcnt
* ob_type

**3.13GIL無効化後**
* ob_tid
* __padding
* ob_mutex
* ob_gc_bits
* ob_ref_local
* ob_ref_shared
* ob_type

## 初めに
Python3.12までの世界線のPyObjectは、`参照カウンタを管理する「ob_refcnt」`と、`対象のオブジェクト型を指すポインタ「ob_type」`の2つのメンバーのみと、自分が見ても理解できるくらい非常にシンプルなものでした。

https://docs.python.org/ja/3.12/c-api/structures.html#c.PyObject

https://docs.python.org/ja/3.12/c-api/typeobj.html#c.PyObject.ob_refcnt


ただ、Python3.13の世界線からは、`GILの無効化`というとんでもアップデートが来たこともあり、上記PyObjectもシンプルなんて言ってられない構造になりました。

```c
struct _object {
  _PyObject_HEAD_EXTRA
  uintptr_t ob_tid;         // owning thread id (4-8 bytes)
  uint16_t __padding;       // reserved for future use (2 bytes)
  PyMutex ob_mutex;         // per-object mutex (1 byte)
  uint8_t ob_gc_bits;       // GC fields (1 byte)
  uint32_t ob_ref_local;    // local reference count (4 bytes)
  Py_ssize_t ob_ref_shared; // shared reference count and state bits (4-8 bytes)
  PyTypeObject *ob_type;
};
```

[PEP703](https://peps.python.org/pep-0703/#reference-counting)から拝借

今回は、そんな新しいPyObjectの各メンバの役割をざっくりと知っていこうの会です。
(C実装のコメントを見るだけで多少わかってしまう気はしますが...)

:::note alert
この記事で紹介する内容は、実験的に投入されたPython3.13に対しての記事です。
今後変更になる可能性は十分にあるため、最終的にはドキュメントを見ていただくようお願いします。
:::

## どう変わったの参照カウンタ
各メンバーを見ていく前に、なぜこのような変更になったのかをざっくりと解説します。

今までの`ob_refcnt` + `GIL`から、Python3.13のGIL無効化からは、`Biased reference counting`という方式に変わりました。
これは、オブジェクト生成したスレッドを「所有スレッド」とし
* 所有スレッドからの参照->非アトミックで「ローカル参照カウント」を変更
* 所有スレッド以外からの参照->アトミックで「共有参照カウント」を変更

という制御機構となります。

## 各メンバーについて
では、上記を踏まえて各PyObjectのメンバーの役割をさらっと見ていきます(ほんとさらっと)
ただ、おそらく同じであろうob_type君は`尺の都合`によりスキップします。

### ob_tid君
上記の「所有スレッド」の値を保持するメンバーになります。
実際には、この値は[_Py_ThreadId](https://github.com/python/cpython/blob/v3.13.1/Include/object.h#L241)関数から取得したThreadId値となります。

ちなみに、このob_tidの値に「0」が設定されることがあり、その場合は所有者なしの状態になります。

### __padding君
[PEP703](https://peps.python.org/pep-0703/)にて特に言及がなかったので、コメントにもあるように、ただのパディングのようです。

### ob_mutex君
[PEP703](https://peps.python.org/pep-0703/)からは1バイトのオブジェクトごとのロックの提供となっています。そのため上記の共有参照カウント変更時にアトミック制御のために用いられる値になるかと思います。

また、Mutexの変更を行うC-APIも3.13から追加されております。

https://docs.python.org/ja/3/c-api/init.html#c.PyMutex

### ob_gc_bits君
オブジェクトのGCステータスを管理する値になります。
各ビットにフラグが立っているかで管理されるようで、設定値としては、以下になります。

https://github.com/python/cpython/blob/main/Include/internal/pycore_gc.h#L38C1-L54C7

(複数フラグが立つことがあるのかしら)

### ob_ref_local君
上記の「ローカル参照カウント」の値になります。
また、この値がUINT32_MAXより大きな値で設定された場合`永続オブジェクト`となります。

https://docs.python.org/ja/3/c-api/refcounting.html#c.Py_SET_REFCNT

基本的に、Pythonの標準で定義されるオブジェクト(True, False等)が永続オブジェクトとして定義されるようです。

また、`永続オブジェクト`は、参照カウンタ系の制御からはノータッチとなります。

https://docs.python.org/ja/3/c-api/refcounting.html#c.Py_INCREF

### ob_ref_shared君
上記の「共有参照カウント」の値になります。所有スレッド以外のスレッドからの参照時には、この値が変更されます。

また、下位2ビットはオブジェクトに対する参照状態が、「default」「weakrefs」「queued」「merged」の何れかの状態を示すために使用されます。

## まとめ
3.13のGIL無効化より、PyObjectの各メンバーは以下のようになりました。
* ob_tid
    * 所有スレッド
* __padding
    * 予備値
* ob_mutex
    * ロック機構
* ob_gc_bits
    * GCステータス
* ob_ref_local
    * ローカル参照カウント
* ob_ref_shared
    * 共有参照カウント
* ob_type
    * オブジェクト型ポインタ

## 終わりに
ほぼ公式Docを参照するマン！になってしまいましたが、この記事で、3.13のGIL無効化から参照カウンタの管理変わるんやなぁ...程度に認識いただけたら幸いです。
(暇があったら、上記をコードレベルで確かめてみたいなぁ感...)

また、上記に関して、より詳しく知りたい方はPEP703を参照ください！

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