1. 概要:
Python の GC モジュールは主に「参照カウント」を使用してガベージを追跡し、リサイクルします。 参照カウントに基づいて、「マーク アンド スイープ」 を通じてコンテナ オブジェクトによって生成される循環参照の問題も解決できます。 「世代別コレクション」を使用してスペースと時間を交換し、ガベージ コレクションの効率をさらに向上させます。
2. 参照数
Python では、ほとんどのオブジェクトのライフサイクルは、オブジェクトの参照カウントを通じて管理されます。広い意味では、参照カウントもガベージ コレクション メカニズムであり、最も直観的でシンプルなガベージ コレクション テクノロジでもあります。
原則: オブジェクトへの参照が作成またはコピーされると、オブジェクトの参照カウントは 1 ずつ増加し、オブジェクトの参照が破棄されると、オブジェクトの参照カウントは 0 に減らされます。 , これは、オブジェクトが誰からも使用されなくなり、そのオブジェクトが占有していたメモリを解放できることを意味します。
メモリの割り当てと解放のたびに参照カウントを管理するには、参照カウントを追加する必要がありますが、他の主流のガベージ コレクション テクノロジと比較して、参照カウントには最大の利点の 1 つがあります。それは、メモリがなくなったら「リアルタイム」であるということです。それを指す参照はすぐにリサイクルされます。他のガベージ コレクション カウントは、無効なメモリをリサイクルできる前に、特定の特殊な条件 (メモリ割り当ての失敗など) に達する必要があります。
参照カウント メカニズムの実行効率の問題: 参照カウント メカニズムによって発生する参照カウントを維持するための追加操作は、メモリの割り当てと解放、および Python の実行中に実行される参照割り当ての数に正比例します。 「マーククリア」や「ストップコピー」などの他の主流のガベージ コレクション メカニズムと比較すると、これらのテクノロジによって生じる追加の操作は基本的にリサイクルされるメモリの量にのみ関係するため、これは弱点となります。
実行効率が参照カウント メカニズムの単なる弱点である場合、残念なことに、参照カウント メカニズムには依然として致命的な弱点があり、まさにこの弱点のため、騎士道的なガベージ コレクションには参照カウントが含まれていない可能性があります。これは循環参照 (相互参照とも呼ばれます) です。
問題の説明:
循環参照により、オブジェクトのグループの参照カウントがゼロ以外になることがあります。ただし、これらのオブジェクトは実際には外部オブジェクトから参照されず、相互に参照するだけです。これは、このオブジェクトのグループをもう誰も使用しないことを意味し、このオブジェクトのグループが占有しているメモリ空間はリサイクルされる必要があります。その後、相互参照の存在により、各オブジェクトの参照カウントが 0 にならず、メモリが占有されます。これらのオブジェクトは決して解放されません。例:
a = [] b = [] a.append(b) b.append(a) print a [[[…]]] print b [[[…]]]
これは致命的であり、手動メモリ管理によって引き起こされるメモリ リークと何ら変わりません。
この問題を解決するために、Python は参照カウントの欠点を補う他のガベージ コレクション メカニズム、つまり「マーク スイープ」と「世代別コレクション」という 2 つのコレクション テクノロジを導入しました。
3. マーククリア
「Mark-Clear」は循環参照の問題を解決するものです。他のオブジェクト (リスト、セット、辞書、クラス、インスタンスなど) への参照を含む可能性のあるコンテナ オブジェクトは、循環参照を生成する可能性があります。
2 つのオブジェクトの参照カウントが両方とも 1 であっても、それらの間に循環参照しかない場合は、両方のオブジェクトをリサイクルする必要があるという事実を認識する必要があります。つまり、それらの参照カウントは非ゼロとして表示されます。ただし、実際の有効な参照数は 0 です。まず循環参照を削除する必要があります。そうすれば、これら 2 つのオブジェクトの有効な数が表示されます。 2 つのオブジェクトが A と B であるとします。B への参照があるため、A から開始し、B の参照カウントを 1 つ減らしてから B に到達します。B には A への参照があるためです。 A の参照も 1 減算されます。このようにして、円形参照オブジェクト間のリングの削除が完了します。
しかし、問題があります。オブジェクト A に C へのオブジェクト参照があるが、C は A を参照していないとします。C の参照カウントが 1 減算され、A が最終的にリサイクルされなかった場合、明らかに、誤って参照を減分したことになります。 C を 1 ずつカウントします。これにより、将来のある時点で C へのダングリング参照が発生します。このため、A を削除せずに C の参照カウントを復元する必要があります。このような解決策を採用すると、参照カウントの維持の複雑さが指数関数的に増加します。
原則: 「Mark-Clear」は、実際の参照カウントを変更するのではなく、コレクション内のオブジェクトの参照カウントのコピーを作成し、オブジェクトによって参照されるコピーを変更する、より良いアプローチを採用します。コピーに加えられた変更は、オブジェクトの寿命の維持には影響しません。
このカウント コピーの唯一の目的は、ルート オブジェクト コレクションを見つけることです (このコレクション内のオブジェクトはリサイクルできません)。ルート オブジェクト セットが正常に見つかると、現在のメモリ リンク リストがまず 2 つに分割され、一方のリンク リストはルート オブジェクト セットを保持してルート リンク リストになり、もう一方のリンク リストは残りのオブジェクトを保持して到達不能リンク リストになります。 。 2 つのリンク リストに分割される理由は、次の考慮事項に基づいています。現在の到達不可能なオブジェクトには、ルート リンク リスト内のオブジェクトが含まれている可能性があり、これらのオブジェクトはプロセス内でマークされるとリサイクルできません。そのようなオブジェクトが見つかった場合は、そのオブジェクトを到達不能リンク リストからルート リンク リストに移動します。マーキングが完了すると、到達不能リンク リスト内の残りのオブジェクトはすべて真のガベージ オブジェクトとなり、その後のガベージ コレクションは制限するだけで済みます。到達不能なリンクリストにアクセスするだけです。
4. 世代別リサイクル
背景: 世代別ガベージ コレクション テクノロジは、1980 年代初頭に開発されたガベージ コレクション メカニズムです。開発に使用される言語、種類、サイズに関係なく。プログラムに関しては、すべてに 1 つの共通点があります。つまり、特定の割合のメモリ ブロックのライフ サイクルは比較的短く、通常は数百万のマシン命令ですが、残りのメモリ ブロックのライフ サイクルは比較的長く、プログラムの開始から終了まで続く場合もあります。 。
「マーククリア」などの以前のガベージ コレクション メカニズムから判断すると、 このガベージ コレクション メカニズムによってもたらされる追加の操作は、実際にはシステム内のメモリ ブロックの総数に関係しています。逆に、リサイクルするメモリ ブロックが少ない場合は、ガベージ検出の方が追加のアクション よりも追加の操作が少なくなります。ガベージ コレクションの効率を向上させるために、「スペース フォー タイム戦略」が採用されています。
原則: システム内のすべてのメモリ ブロックを、その生存時間に応じて異なるコレクションに分割します。各コレクションは、「世代」の生存時間が増加するにつれて、ガベージ コレクションの頻度が減少します。言い換えれば、オブジェクトの存続期間が長ければ長いほど、そのオブジェクトがガベージになる可能性は低くなり、そのオブジェクトに対するガベージ コレクションの頻度を減らす必要があります。この生存時間はどのように測定するか: 通常、オブジェクトがより多くのガベージ コレクションを実行した場合、そのオブジェクトの生存時間は長くなると結論付けることができます。
例:
3 回のガベージ コレクション後にメモリ ブロック M がまだ残っている場合、メモリ ブロック M をセット A に分割し、新しく割り当てられたメモリはセット B に分割します。ガベージ コレクションが機能し始めると、ほとんどの場合、コレクション B のみがガベージ コレクションされ、長時間経過後にコレクション A がガベージ コレクションされます。これにより、ガベージ コレクション メカニズムが処理する必要のあるメモリが減り、当然効率が向上します。このプロセスでは、セット B の一部のメモリ ブロックは、生存時間が長いためセット A に転送されます。もちろん、実際にはセット A にいくつかのガベージがあり、これらのガベージの収集はこの世代メカニズムによるものです。遅れました。
Python には合計 3 つの「世代」があり、これは Python が実際に 3 つのリンク リストを維持することを意味します。詳細については、Python ソース コードを参照してください。