Python Languageガベージコレクション


備考

Pythonのガベージコレクタ(3.5以降)は、その核となる単純な参照カウント実装です。オブジェクト(たとえばa = myobject )への参照を作成するたびに、そのオブジェクト( a = myobject )の参照カウントがインクリメントされます。参照が削除されるたびに、参照カウントが減少し、参照カウントが0に達すると、そのオブジェクトへの参照を保持するものは何もないことが分かり、解放することができます。

Pythonのメモリ管理がどのように動作するかについての一般的な誤解の1つは、 delキーワードはオブジェクトのメモリを解放することです。本当じゃない。実際には、 delキーワードは単にオブジェクトrefcountを減らすだけです。つまり、refcountが0に達するのに十分な時間を呼び出すと、オブジェクト内でガベージコレクションが行われる可能性があります。 )。

Pythonはオブジェクトを初めて必要とするときに積極的にオブジェクトを作成またはクリーンアップします。割り当てを実行するとオブジェクトのメモリはその時点で割り当てられます(cpythonは特定のタイプのオブジェクトを再利用することがあります。ほとんどの場合、空きオブジェクトプールを保持せず、必要なときに割り当てを実行します)。同様に、refcountが0にデクリメントされると、GCはそれをクリーンアップします。

世代別ガベージコレクション

1960年代にJohn McCarthyは、Lispで使用されているrefcountingアルゴリズムを実装したときに、ガベージコレクションを参照カウントする際に致命的な欠陥を発見しました:2つのオブジェクトが循環参照でお互いを参照するとどうなりますか?彼らがいつもお互いを参照する場合、外部参照がなくても、どうしてこれら2つのオブジェクトをガベージコレクションすることができますか?この問題は、リングバッファまたは二重リンクリストの任意の2つの連続したエントリなど、任意の循環データ構造にも及ぶ。 Pythonはこの問題を、 世代別ガベージコレクションと呼ばれる別のガベージコレクションアルゴリズムで少し面白いように修正しようとします。

本質的に、Pythonでオブジェクトを作成するときには、そのオブジェクトを二重リンクリストの最後に追加します。時にはPythonがこのリストをループして、リスト内のオブジェクトがどのオブジェクトを参照しているかをチェックし、それらがリストにも含まれているかどうかをチェックします(なぜそれらが一瞬でないのか分かります)。現時点では、物事がいつ動くかを決めるヒューリスティックがありますが、物事を単純にするために単一のコレクションの後にあると仮定しようとします。0より大きいrefcountを持つものは、 "Generation 1" (これは、すべてのオブジェクトが常に世代0のリストにあるわけではないためです)、このループはあまり頻繁に適用されません。これは、世代別ガベージコレクションが入る場所です。デフォルトでは3つの世代がPythonにあります(オブジェクトの3つのリンクされたリスト)。最初のリスト(世代0)にはすべての新しいオブジェクトが含まれます。 GCサイクルが発生してオブジェクトが収集されない場合、それらは第2のリスト(第1世代)に移動し、第2のリストでGCサイクルが発生し、それらがまだ収集されない場合、第3のリスト(世代2 )。第3世代のリスト(「第2世代」と呼ばれているのは、ゼロインデックス処理なので)は、最初の2つよりもはるかに少ない頻度でガベージコレクションされます。オブジェクトが長生きするとGCedになる可能性は低く、アプリケーションの存続期間中にGCされるので、1回のGC実行ごとに時間を浪費することはありません。さらに、ほとんどのオブジェクトは比較的早くガベージ・コレクションされることがわかりました。これからは、若いうちに亡くなるので、これらの「良いもの」と呼ぶことにします。これは「弱世代仮説」と呼ばれ、60年代に初めて観察されたものです。

早急に:最初の2世代と異なり、長寿命の第3世代のリストは定期的なスケジュールでガベージコレクションされません。保留中の長いオブジェクト(第3世代のリストにあるものの、実際にはGCサイクルをまだ持っていないオブジェクト)と、リスト内の長命オブジェクトの合計の比率が25%を超えたときにチェックされます。これは、3番目のリストは無制限であるため(実際には別のリストに移動されることはないため、実際にガベージコレクションされたときにのみ消えてしまいます)、長寿命のオブジェクトをたくさん作成するアプリケーションの場合、GCサイクル3番目のリストにはかなり長い時間がかかることがあります。比率を使用することによって、「オブジェクトの総数における償却された線形性能」を達成する。リストが長くなればなるほどGCは長くなりますが、GCを実行する頻度は少なくなります(ここでは、Martin VonLöwisによるこのヒューリスティックの最初の提案が続きます)。第3世代または「成熟した」リスト上でガベージコレクションを実行する行為は、「完全ガベージコレクション」と呼ばれます。

だから世代別ガーベジコレクションは、GCを必要としそうもないオブジェクトをスキャンする必要がないため、時間を浪費しますが、循環参照を破るのにどのように役立ちますか?おそらくそれほどうまくいかないかもしれません。これらの参照サイクルを実際に破る関数は次のようになります

/* Break reference cycles by clearing the containers involved.  This is
 * tricky business as the lists can be changing and we don't know which
 * objects may be freed.  It is possible I screwed something up here.
 */
static void
delete_garbage(PyGC_Head *collectable, PyGC_Head *old)

世代別ガベージコレクションがこれを助ける理由は、リストの長さを別々のカウントとして保持できることです。新しいオブジェクトを世代に追加するたびに、このカウントを増やし、オブジェクトを別の世代に移動するか、またはオブジェクトを別の世代に移動するか、それをデアロックします。理論的には、GCサイクルの終わりに、このカウント(最初の2世代の場合)は常に0にする必要があります。そうでない場合は、リスト内の何かが循環参照の何らかの形であり、取り除くことができます。しかし、もう一つの問題があります。残ったオブジェクトにPythonの魔法メソッド__del__があるとどうなりますか? __del__は、Pythonオブジェクトが破棄される__del__に呼び出されます。循環参照で2つのオブジェクトが持っている場合は、 __del__メソッドを、私たちは1を破壊すると、他の人を壊さないことを確認することはできません__del__メソッドを。考案された例として、次のように書いたと想像してみてください。

class A(object):
    def __init__(self, b=None):
        self.b = b
 
    def __del__(self):
        print("We're deleting an instance of A containing:", self.b)
     
class B(object):
    def __init__(self, a=None):
        self.a = a
 
    def __del__(self):
        print("We're deleting an instance of B containing:", self.a)

AのインスタンスとBのインスタンスを互いにポイントするように設定し、それらは同じガベージコレクションサイクルに終わるでしょうか?私たちがランダムに1つを選択し、最初にAのインスタンスをdeallocしたとしましょう。 Aの__del__メソッドが呼び出され、それが出力され、Aが解放されます。次にBに来て、 __del__メソッドと呼んで、おっと! Segfault! Aは存在しません。この問題を解決するには、まず__del__メソッドを残して__del__ 、次にすべてを実際に無効にする別のパスを呼び出すことでこれを修正することができます。しかし、これは別の問題を導入します:1つのオブジェクト__del__メソッドがGCedしようとしている他のどこかで私たちを参照していますか?まだ参照サイクルがありますが、実際にはどちらのオブジェクトもGCが使用されていなくても実際にGCすることはできません。オブジェクトが循環データ構造の一部でなくても、それ自身の__del__メソッドで復活できることに注意してください。 Pythonはこれをチェックし、 __del__メソッドが呼び出された後にオブジェクトrefcountが増加した場合にGCingを停止します。

これをCPythonで処理するには、 __del__なゴミのグローバルリストにそれらのGC __del__なオブジェクト(何らかの形式の循環参照と__del__メソッドを持つもの)を貼り付けて、それを永遠に残すことです。

/* list of uncollectable objects */
static PyObject *garbage = NULL;

ガベージコレクション 関連する例