10. ガベージコレクター
#10. ガベージコレクター
CPython は、メインのメモリ管理メカニズムとして参照カウントを使用します。参照カウントでは、ほとんどのオブジェクトは、最後の強い参照が消えるとすぐに破棄されます。
参照カウントには大きな制限が 1 つあります。それは、参照サイクルを単独で再利用できないことです。
ガベージ コレクターは、コンテナ オブジェクトの到達不能なサイクルを見つけて再利用するために存在します。これは参照カウントを補足するものであり、それに代わるものではありません。
10.1 参照カウントにヘルプが必要な理由
参照カウントがゼロになるのは、オブジェクトを指す強参照がない場合のみです。
これは通常のオブジェクト グラフで機能します。```python x = [] del x
サイクルでは機能しません。```python
a = []
b = []
a.append(b)
b.append(a)
del a
del b
```2 つの名前が削除された後も、リストは引き続き相互参照します。```text
list A ---> list B
list B ---> list A
```それらの参照カウントはゼロ以外のままです。しかし、ライブ Python コードはそれらに到達できません。
参照カウントではローカル所有権が確認されます。ガベージ コレクションでは到達可能性が確認されます。
## 10.2 ガベージコレクターが追跡するもの
CPython は、循環ガベージ コレクター内のすべてのオブジェクトを追跡する必要はありません。
他の Python オブジェクトへの参照を含めることができないオブジェクトは、それ自体でサイクルを形成できません。例には、多くの整数、浮動小数点、単純な文字列が含まれます。
コレクターは主にコンテナーのようなオブジェクトを追跡します。```text
list
dict
set
tuple containing references
function
class
instance
frame
generator
coroutine
traceback
some extension objects
```オブジェクトがサイクルに参加できる場合、オブジェクトは GC 追跡を必要とします。
例えば:```python
x = 123
```整数オブジェクトは、コンテナー サイクルを作成するような方法で他の Python オブジェクトを指しません。
しかし:```python
x = []
x.append(x)
```リストはそれ自体を指します。これがサイクルです。
## 10.3 セルフサイクル
最も単純な参照サイクルは自己サイクルです。```python
x = []
x.append(x)
```構造は次のとおりです。```text
x ---> list
^ |
| |
+--+
```次に、外部名を削除します。```python
del x
```リストにはまだそれ自体への参照が含まれています。その参照カウントはゼロより大きいままです。
プログラム変数はこれに到達できませんが、参照カウントだけでは破壊できません。周期的なガベージ コレクターはそれを検出する必要があります。
## 10.4 複数オブジェクトのサイクル
サイクルには複数のオブジェクトが関与することがよくあります。```python
class Node:
def __init__(self):
self.parent = None
self.children = []
root = Node()
child = Node()
root.children.append(child)
child.parent = root
del root
del child
```グラフにアクセスできなくなりますが、参照はグラフ内に残ります。```text
root node ---> children list ---> child node
^ |
| |
+----------- parent -----------+
```このパターンは、ツリー、グラフ、オブジェクト モデル、AST、DOM、キャッシュ、フレーム、クロージャ、例外トレースバックで一般的です。
## 10.5 コレクターはコンテナー上で動作します
循環コレクターは、他のオブジェクトを指すことができるオブジェクトについて推論するだけで済みます。
追跡されている各オブジェクトに対して次の質問を行います。```text
Which Python objects do you reference?
```C レベルでは、拡張タイプはトラバーサル サポートを通じて応答します。 GC 対応型は、含まれる Python 参照にアクセスするトラバーサル関数を提供します。
概念的には:```c
static int
Node_traverse(NodeObject *self, visitproc visit, void *arg)
{
Py_VISIT(self->parent);
Py_VISIT(self->children);
return 0;
}
```コレクターはこれを使用してオブジェクト グラフを移動します。
拡張オブジェクトが Python 参照を所有しており、サイクルの一部にできる場合は、このプロトコルに参加する必要があります。そうしないと、サイクルが漏れる可能性があります。
## 10.6 追跡されるオブジェクトと追跡されないオブジェクト
オブジェクトは、サイクリック コレクターによって追跡される場合もあれば、追跡されない場合もあります。
追跡付きとは、コレクターが収集中に検査する可能性があることを意味します。
Untracked は、コレクタがサイクル検出のためにそれを無視することを意味します。
これは Python から検査できます。```python
import gc
print(gc.is_tracked([]))
print(gc.is_tracked(123))
print(gc.is_tracked("hello"))
```CPython の一般的な出力では、コンテナは追跡されているが、アトミック オブジェクトは追跡されていないことが示されます。 CPython は最適化を適用するため、正確な結果は異なる場合があります。たとえば、一部のコンテナは、アトミック オブジェクトのみを含む場合、追跡されなくなる可能性があります。
重要なルールは概念的なものです。```text
objects that can participate in cycles need tracking
objects that cannot participate in cycles usually do not
```## 10.7 世代別コレクション
CPython の循環ガベージ コレクターは世代別です。
このアイデアは、ほとんどのオブジェクトは早期に死ぬという一般的な観察に基づいています。複数のコレクションを経て生き残ったオブジェクトは、より長く存続する可能性があります。
世代別コレクターは、追跡されたオブジェクトを年齢ごとにグループ化します。若い世代ほど頻繁に収集されます。古い世代は収集される頻度が低くなります。
概念的には:```text
generation 0
newest tracked objects
collected most often
generation 1
objects that survived earlier collection
collected less often
generation 2
older tracked objects
collected least often
```正確な生成設計は、CPython のバージョンによって異なる場合があります。有用なメンタル モデルは、CPython がすべてのコレクションで追跡されているすべてのコンテナーをスキャンすることを回避するということです。
## 10.8 収集のしきい値
の`gc`モジュールはしきい値を公開します。```python
import gc
print(gc.get_threshold())
```しきい値は、自動周期収集をいつ実行するかを決定するのに役立ちます。
それらを変更できます。```python
gc.set_threshold(700, 10, 10)
```コレクションを強制的に実行できます。```python
gc.collect()
```自動周期収集を無効にできます。```python
gc.disable()
```そして再度有効にします:```python
gc.enable()
```コレクターを無効にしても、参照カウントは無効になりません。参照カウントが 0 に達したオブジェクトも破棄されます。コレクターを無効にすると、自動サイクル検出のみが無効になります。
## 10.9 サイクル検出の仕組み
コレクターは、参照カウントがゼロ以外のオブジェクトを単に検索するわけではありません。ほぼすべてのライブ オブジェクトには、ゼロ以外の参照カウントがあります。
代わりに、グループを存続させる参照がそのグループ内からのみ発生するかどうかを計算します。
簡略化されたサイクル検出プロセス:```text
select tracked candidate objects
copy each object's reference count into a temporary field
for each reference from candidate object to candidate object:
subtract one from the target's temporary count
objects with temporary count still positive are reachable from outside
propagate reachability from those externally reachable objects
objects never reached are unreachable cycles
```例:```text
outside ---> A ---> B
^ |
| v
D <--- C
```たとえ`A`、`B`、`C`、 そして`D`サイクルを形成し、外部参照`A`グループ全体に到達可能になります。
しかし:```text
A ---> B
^ |
| v
D <--- C
```外部参照なしで収集可能です。
## 10.10 到達可能なサイクルはゴミではない
サイクルは自動的にガベージにはなりません。```python
a = []
a.append(a)
print(a)
```このオブジェクトは循環的ですが、名前を通じてアクセスできます。`a`。 It must stay alive.
コレクターは、到達不能なサイクルのみを再利用します。
この区別はデータ構造にとって重要です。循環グラフは Python では通常のものであり、有効です。コレクターが存在するため、一般的な場合に手動で破損することなく安全に使用できます。
## 10.11 Finalizers
ファイナライザーはガベージ コレクションを複雑にします。
A finalizer is usually a`__del__`方法:```python
class Resource:
def __del__(self):
print("destroying")
```ファイナライザーはオブジェクトの破棄中に実行されます。 Python コードを実行できます。そのコードは、グローバルへのアクセス、状態の変更、ロックの取得、オブジェクトの作成、さらにはファイナライズ中のオブジェクトの復活さえも行うことができます。
オブジェクトの復活とは、ファイナライザーがオブジェクトを再び到達可能にすることを意味します。```python
saved = None
class Resurrect:
def __del__(self):
global saved
saved = self
```これにより、収集がより複雑になります。
最新の CPython には、循環ガベージを終了するための特定のルールがありますが、実践的なガイダンスはシンプルです。```text
avoid complex __del__ methods
prefer context managers
prefer weakref.finalize for cleanup hooks
```## 10.12 コンテキストマネージャーはリソースにとって優れています
ガベージ コレクションはリソース管理 API ではありません。
使用`with`決定的なクリーンアップの場合:```python
with open("data.txt") as f:
data = f.read()
```これにより、ブロックが終了するとファイルが閉じられます。
ガベージ コレクションのタイミングに依存しないでください。```python
f = open("data.txt")
data = f.read()
f = None
```CPython では、参照カウントによりファイルがすぐに閉じられる場合があります。他の実装では、クリーンアップが後で行われる場合があります。
ロック、ソケット、ファイル、トランザクション、一時ディレクトリ、および外部ハンドルについては、明示的なライフタイム制御を使用します。
##10.13`gc.collect`
`gc.collect()`循環コレクションを強制します。```python
import gc
n = gc.collect()
print(n)
```戻り値は、検出および収集された到達不能オブジェクトの数です。
そのインターフェイスをサポートするバージョンで特定の世代をリクエストできます。```python
gc.collect(0)
gc.collect(1)
gc.collect(2)
```手動収集は次の場合に役立ちます。```text
tests
debugging leaks
interactive experiments
memory-sensitive batch phases
controlled benchmarks
```通常のアプリケーション コードではほとんど必要ありません。
##10.14`gc.get_objects`
`gc.get_objects()`コレクターが認識している追跡オブジェクトを返します。```python
import gc
objs = gc.get_objects()
print(len(objs))
```これは、すべてのライブ Python オブジェクトを返すわけではありません。周期的なガベージ コレクターによって追跡されたオブジェクトを返します。
多くの原子オブジェクトが存在しない可能性があります。
これは、通常のアプリケーション ロジックではなく、オブジェクト グラフとメモリ リークのデバッグに使用します。
##10.15`gc.get_referrers`
`gc.get_referrers(obj)`を直接参照するオブジェクトを返します。`obj`。```python
import gc
x = []
refs = gc.get_referrers(x)
print(refs)
```これは、オブジェクトが生き続ける理由を説明するのに役立ちます。
しかし、慎重に使用する必要があります。デバッグ関数を呼び出すと、一時的な参照とフレームが作成されます。これらは結果に表示される場合があります。`gc.get_referrers`実装の詳細も公開します。スタック フレーム、辞書、リスト、内部オブジェクトを表示できます。
##10.16`gc.get_referents`
`gc.get_referents(obj)`によって直接参照されるオブジェクトを返します`obj`。```python
import gc
x = [[1], [2]]
print(gc.get_referents(x))
```リストの場合、これにはその要素が含まれます。
辞書の場合、これにはキーと値が含まれる場合があります。
この関数は、コレクターが使用するものと同じトラバーサル サポートを使用します。 GC レベルの参照対象は参照されますが、プログラマが想像するすべての意味関係が必ずしも参照されるわけではありません。
## 10.17 収集できないゴミ
一部のオブジェクトは、特定の条件下では到達不可能であることが判明しても、すぐには収集できない場合があります。
歴史的には、ファイナライザーを含むサイクルは特に困難でした。最新の CPython はこれらのケースの多くをより適切に処理しますが、収集できないオブジェクトは依然として拡張タイプや異常な終了動作で表示される可能性があります。
の`gc`モジュールは以下を公開します:```python
import gc
print(gc.garbage)
```デバッグ時に、デバッグ フラグを有効にできます。```python
gc.set_debug(gc.DEBUG_SAVEALL)
```と`DEBUG_SAVEALL`、到達不可能なオブジェクトは次の場所に保存されます。`gc.garbage`解放される代わりに。これは検査には便利ですが、リストをクリアするまで意図的にリークされます。
## 10.18 弱い参照
弱参照を使用すると、オブジェクトを存続させずに観察できます。```python
import weakref
class User:
pass
u = User()
r = weakref.ref(u)
print(r()) # object
del u
print(r()) # None
```弱参照は、ターゲットの強参照数を増加させません。
弱参照は、キャッシュ、オブザーバー リスト、親リンク、および補助メタデータに役立ちます。
親ポインタは弱いことがよくあります。```python
import weakref
class Node:
def __init__(self):
self.children = []
self.parent = None
root = Node()
child = Node()
child.parent = weakref.ref(root)
root.children.append(child)
```これにより、親と子のリンクによる強力なサイクルの形成が回避されます。
##10.19`weakref.finalize`
`weakref.finalize`クリーンアップ ロジックを直接配置せずにクリーンアップ コードを登録します。`__del__`。```python
import weakref
class Resource:
pass
def cleanup(name):
print("cleaning", name)
r = Resource()
finalizer = weakref.finalize(r, cleanup, "resource")
```いつ`r`到達不能になっても、ファイナライザーは実行できます。
多くの場合、これは複雑なコードを書くより安全です`__del__`これは、ファイナライザーがクリーンアップ状態をファイナライズ中のオブジェクトから分離するためです。
それでも、外部リソースは通常、可能であればコンテキスト マネージャーを使用して管理する必要があります。
## 10.20 フレーム、トレースバック、およびサイクル
フレームとトレースバックによってサイクルが作成されることがよくあります。
例:```python
def f():
x = []
raise RuntimeError
try:
f()
except RuntimeError as exc:
saved = exc
```例外はトレースバックを保持することができます。トレースバックはフレームを保持できます。フレームはローカル変数を保持します。ローカル変数には、例外またはそれに関連するオブジェクトが保持される場合があります。
概念的には:```text
exception
traceback
frame
locals
exception
```これにより、大きなオブジェクト グラフを存続させることができます。
最新の Python は、古いバージョンよりも積極的に一部の例外状態をクリアしますが、保存された例外とトレースバックは引き続きメモリを保持できます。
一般的な軽減策:```python
try:
...
except Exception as exc:
...
finally:
exc = None
```または、トレースバックを必要以上に長く保存しないようにします。
## 10.21 クロージャとサイクル
クロージャもサイクルを生み出す可能性があります。```python
def make_func():
items = []
def add(x):
items.append(x)
return items
return add
```内部関数はセルを参照します。セル参照`items`。
より複雑なクロージャでは、関数を逆参照するオブジェクトを参照できます。
実際のプログラムでは、関数、クロージャ、インスタンス、コールバックの循環が一般的です。
コレクターはそれらの多くを処理しますが、グラフを理解することは、メモリが予期せず増加した場合に役立ちます。
## 10.22 拡張タイプと GC サポート
Python オブジェクトへの参照を所有する C 拡張タイプには、循環 GC サポートが必要な場合があります。
多くの場合、必要な要素には次のものが含まれます。```text
Py_TPFLAGS_HAVE_GC
tp_traverse
tp_clear
GC-aware allocation
GC-aware deallocation
```トラバーサル関数は、このオブジェクトがどのオブジェクトを参照しているかをコレクターに伝えます。```c
static int
Box_traverse(BoxObject *self, visitproc visit, void *arg)
{
Py_VISIT(self->value);
return 0;
}
```Clear 関数は収集中に参照を中断します。```c
static int
Box_clear(BoxObject *self)
{
Py_CLEAR(self->value);
return 0;
}
```割り当て解除者はオブジェクトの追跡を解除し、所有されている参照を安全にクリアする必要があります。
概念的な形状:```c
static void
Box_dealloc(BoxObject *self)
{
PyObject_GC_UnTrack(self);
Box_clear(self);
Py_TYPE(self)->tp_free((PyObject *)self);
}
```これは簡略化されています。実際の拡張コードは、正確な C API 要件に従う必要があります。
##10.23`Py_VISIT`
`Py_VISIT`トラバーサル関数内で使用されます。```c
static int
Node_traverse(NodeObject *self, visitproc visit, void *arg)
{
Py_VISIT(self->left);
Py_VISIT(self->right);
Py_VISIT(self->parent);
return 0;
}
```フィールドが NULL でないかどうかをチェックし、訪問者に渡します。
正しいトラバーサル関数は、サイクルに参加できるすべての含まれる Python オブジェクト参照を訪問する必要があります。
フィールドが 1 つ欠けていると、サイクルがコレクターに見えなくなる可能性があります。
##10.24`Py_CLEAR`
`Py_CLEAR`所有されている参照を安全にクリアします。
単純なクリア操作:```c
Py_DECREF(self->value);
self->value = NULL;
```微妙な問題を抱えています。の`Py_DECREF`ファイナライザーを通じて任意のコードを実行できます。そのコードは次のことを観察する可能性があります`self`前に`self->value`に設定されています`NULL`。`Py_CLEAR`フィールドを次のように設定します`NULL`まず古い参照をデクリメントします。
概念的には:```c
tmp = self->value;
self->value = NULL;
Py_XDECREF(tmp);
```使用`Py_CLEAR`コンテナまたはデアロケータ内の参照を破壊するとき。
## 10.25 収集とパフォーマンス
サイクリックコレクターにはコストがかかります。追跡されたコンテナをスキャンし、参照を追跡します。
ほとんどのプログラムでは、これを有効のままにしておく必要があります。ただし、一部のワークロードでは調整される場合があります。```text
short-lived batch jobs
allocation-heavy parsers
large object graph construction
scientific pipelines
services with known allocation phases
```例:```python
import gc
gc.disable()
try:
build_large_graph()
finally:
gc.enable()
gc.collect()
```このパターンは、フェーズでサイクルなしで多くのコンテナーが作成されることがわかっている場合に役立ちます。サイクルが蓄積すると痛む可能性もあります。
チューニング前に測定してください。
## 10.26 メモリリークと保持された参照
すべてのメモリ増加が C の意味でのリークであるわけではありません。
プログラムが誤って参照を保持する場合があります。```python
cache = []
def handle(request):
cache.append(request)
```コレクターは到達可能なオブジェクトを解放できません。もし`cache`成長し続けるため、それらのオブジェクトは生きています。
真の C レベルのリークは、実装によってメモリまたは参照が失われることを意味します。
Python レベルの保持バグは、プログラムに、不要になったオブジェクトへの到達可能な参照がまだ残っていることを意味します。
メモリのデバッグは、多くの場合、次の質問から始まります。```text
Is the object unreachable but not collected?
Or is something still referring to it?
```使用`gc.get_referrers`それに答えるための、tracemalloc、オブジェクト グラフ ツール、およびヒープ インスペクション。
## 10.27 一般的なサイクルパターン
サイクルの一般的な原因には次のものがあります。
|パターン |形状 |
| ---------------------- | ------------------------------------------------ |
|親リンクと子リンク |親 -> 子 -> 親 |
|二重リンクリスト |ノード A -> ノード B -> ノード A |
|グラフ構造 |任意のサイクル |
|オブザーバーのコールバック |オブジェクト -> コールバック -> バインドされたメソッド -> オブジェクト |
|例外 |例外 -> トレースバック -> フレーム -> ローカル |
|クロージャ |関数 -> クロージャーセル -> オブジェクト -> 関数 |
|記述子 |クラス -> 記述子 -> クラス関連の状態 |
| C 拡張オブジェクト |ネイティブ オブジェクト -> Python オブジェクト -> ネイティブ ラッパー |
周期は正常です。問題は、到達不能なサイクルが収集可能かどうか、および決定的なクリーンアップが必要な外部リソースがそれらのサイクルに含まれているかどうかです。
## 10.28 実践的なルール
Python コードの場合:
|状況 |好ましいアプローチ |
| ---------------------- | -------------------------------------------------- |
|外部リソース |使用`with`|
|親ポインタ |考慮する`weakref`|
|キャッシュ |使用`weakref.WeakValueDictionary`適切な場合 |
|存続期間の長い例外 |トレースバックを不必要に保持しないようにする |
|メモリのデバッグ |使用`gc`、`tracemalloc`、およびリファラーの検査 |
|クリーンアップフック |コンテキストマネージャーを使用するか、`weakref.finalize`|
|大規模割り当てフェーズ |測定後にのみ GC を調整する |
C 拡張コードの場合:
|状況 |必要な規律 |
| ------------------------------- | --------------------------------- |
|型は Python 参照を所有します |適切な割り当て解除を実装する |
|型はサイクルを形成できる | GC サポートを追加 |
|タイプは GC をサポートします |埋め込む`tp_traverse`正しく |
|タイプはコレクションに参加します |埋め込む`tp_clear`正しく |
|参照の破壊 |使用`Py_CLEAR`|
|フィールドを横断 |使用`Py_VISIT`|
| GC オブジェクトの割り当てを解除中 |クリアする前にアントラック |
## 10.29 メンタルモデル
このモデルを使用します。```text
Reference counting handles local lifetime.
Cyclic GC handles unreachable container cycles.
The collector only sees tracked objects.
Tracked objects must describe their outgoing references.
Reachable cycles remain alive.
Unreachable cycles can be collected.
Finalizers and extension types complicate collection.
Resource lifetime should be explicit.
```このモデルは、ほとんどの CPython オブジェクトがすぐに消える理由、一部の循環構造がコレクションまで存続する理由、およびその理由を説明します。`__del__`注意が必要です。また、C 拡張タイプが Python 参照を所有する場合に GC プロトコルに参加しなければならない理由についても説明します。
## 10.30 概要
CPython のガベージ コレクターは、参照カウントではサイクルを再利用できないために存在します。コンテナのようなオブジェクトを追跡し、それらの間の参照を分析し、到達不可能なグループを見つけて、それらを安全にクリアします。
コレクターは世代別であり、`gc`モジュールであり、オブジェクト モデルと深く結びついています。 Python プログラマーは主に、サイクル、ファイナライザー、弱参照、およびリソースの有効期間を理解する必要があります。 C 拡張機能の作成者は、その型がサイクルに参加できる場合、トラバーサルとクリアを正しく実装する必要があります。