Pythonは、すべての変数などが参照しているオブジェクトを正確に把握し、それぞれのオブジェクトが参照されている数を厳密に記録しています。この、記録されている参照の数を、参照カウント と言います。参照カウントが 0 になると、そのオブジェクトはすでに利用されていないことが明らかなので、Pythonはそのオブジェクトを開放します。
オブジェクトの参照カウントは、sys.getrefcount() で調べられます。
次の例では、sys.getrefcount()
を使って、True
オブジェクトが参照されている数を調べています。起動した直後の状態ですが、すでに 202ヶ所から参照されています。
Python 3.11.7 (v3.11.7:fa7a6f2303, Dec 4 2023, 15:22:56) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getrefcount(True)
202
True
を変数 var1
に代入すると、参照カウントが 1 増加します。
>>> var1 = True
>>> sys.getrefcount(True)
203
変数 var1
を削除するともとに戻ります。
>>> del var1
>>> sys.getrefcount(True)
202
同じことを、Python3.12でも実行してみましょう。
Python 3.12.0 (v3.12.0:0fb18b02c8, Oct 2 2023, 09:45:56) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getrefcount(True)
4294967295
>>> var1 = True
>>> sys.getrefcount(True)
4294967295
>>> del var1
>>> sys.getrefcount(True)
4294967295
>>>
なにをやっても参照カウントは変化しなくなってしまいました。
PEP 683 – Immortal Objects, Using a Fixed Refcount¶
Python3.12では、PEP 683: 固定参照カウントによる永続オブジェクト が導入され、特に利用頻度の高い一部の変更不能オブジェクト(None
, True
, False
, -5
から 256
までの整数や組み込みデータ型など)は、実行時に参照カウントを更新しないように変更されました。 対象オブジェクトの参照カウントは、常に 4294967295 (=0xFFFFFFFF)
固定となります。
インスタグラムでのメモリ削減効果¶
この変更は Django を利用している Instagramから提案されたものです。InstagramではDjangoのサーバプロセスを複数起動してサービスを提供していますが、本来ならばすべてのサーバで共有されているはずのメモリが徐々に共有されなくなり、サーバごとのメモリ使用量が時間とともに増大するという現象に悩まされていました。
(画像は Introducing Immortal Objects for Pythonから)
こういったサービスでは、まず最初に一つDjangoプロセスを起動し、そのプロセスをコピーして子プロセスを作成します。この方式では、子プロセスは親プロセスのメモリを共有するのでメモリ使用量を減らせるというメリットがありますが、Pythonの場合は各プロセスが参照カウントの更新を行うため、メモリを共有できずに子プロセスはそれぞれ専用のメモリを持つようになってしまいます。
しかし、None
などの汎用的なオブジェクトを参照カウントの対象外とすれば、その部分のメモリは全く更新されなくなります。その結果、これまで更新されていた部分のメモリをすべてのプロセスで共有できるので、全体的なメモリ効率が大きく向上するようになりました。
(画像は Introducing Immortal Objects for Pythonから)
GIL除去に向けて¶
現在 Pythonでは PEP 703 グローバル・インタプリタ・ロックを除去可能に の解説 や Python 3.12の新機能(その5) PEP 684: インタープリター別GIL など、GILの影響を軽減する試みが行われています。参照カウントの更新はPythonがGILを必要とする大きな要因でもありますので、今回の変更は将来への大きな助けとなるでしょう。