Python のガベージ コレクション メカニズム (GC-GarbageCollection)

Python の自動ガベージ コレクション メカニズムのおかげで、Python でオブジェクトを作成したときにオブジェクトを手動で解放する必要はありません。これは開発者にとって非常に使いやすいため、開発者は低レベルのメモリ管理に注意を払う必要がありません。ただし、ガベージ コレクションのメカニズムを理解していないと、作成する Python コードは多くの場合非常に非効率なものになります。

ガベージ コレクション アルゴリズムには、主に参照カウント、マークスイープ、世代別コレクションなど、数多くあります。Python では、ガベージ コレクション アルゴリズムは主に [参照カウント] に基づいており、[マーククリア] と [世代別コレクション] によって補足されます。

1. 参照カウント

原則として、各オブジェクトは ob_ref を保持し、現在のオブジェクトが参照された回数を記録する、つまり、このオブジェクトを指す参照の数を追跡するために使用されます。 0 の場合、メモリは Python 仮想マシンによって破壊されます。

1. 参照カウント +1

次の 4 つの状況が発生した場合、オブジェクトの参照カウンタは +1 されます。

* 对象被创建  a=14
* 对象被引用  b=a
* 对象被作为参数,传到函数中   func(a)
* 对象作为一个元素,存储在容器中   List={
    
    a,”a”,”b”,2}

2. 参照カウント -1

次の 4 つの状況が発生した場合、オブジェクトの参照カウンタは -1 になります。

* 当该对象的别名被显式销毁时  del a
* 当该对象的引别名被赋予新的对象,   a=26
* 一个对象离开它的作用域,例如 func函数执行完毕时,函数里面的局部变量的引用计数器就会减一(但是全局变量不会)
* 将该元素从容器中删除时,或者容器被销毁时。

3. コード戦闘

sys パッケージの getrefcount() を使用して、名前によって参照されるオブジェクトの現在の参照カウントを取得することもできます (getrefcount() 自体が参照カウントを 1 つ増やすことに注意してください)。

import sys
 
class A():

    def __init__(self):
        pass
 
print("创建对象 0 + 1 =", sys.getrefcount(A()))

a = A()
print("创建对象并赋值 0 + 2 =", sys.getrefcount(a))

b = a
c = a
print("赋给2个变量 2 + 2 =", sys.getrefcount(a))

b = None
print("变量重新赋值 4 - 1 =", sys.getrefcount(a))

del c
print("del对象 3 - 1 =", sys.getrefcount(a))

d = [a, a, a]
print("3次加入列表 2 + 3 =", sys.getrefcount(a))


def func(c):
    print('传入函数 1 + 2 = ', sys.getrefcount(c))
func(A())

4. 参照カウント方法には長所と短所があります。

4.1. 利点
* 高效
* 运行期没有停顿,也就是实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。
* 对象有确定的生命周期
* 易于实现
4.2. 欠点
* 需要为对象分配引用计数空间,增大了内存消耗。
* 当需要释放的对象比较大时,如字典对象,需要对引用的所有对象循环嵌套调用,可能耗时比较长。
* 循环引用。这是引用计数的致命伤,引用计数对此是无解的,因此必须要使用其它的垃圾回收算法对其进行补充。

ここに画像の説明を挿入

2.マーククリア

「Mark-Sweet」アルゴリズムは、トレース GC テクノロジーに基づいたガベージ コレクション アルゴリズムです。参照カウント アルゴリズムでは、循環参照の問題を解決できません。オブジェクトが循環参照されると、カウンタが 0 にならないため、リサイクルできないという問題が発生します。
Python は、「マーククリア」(マークとスイープ) アルゴリズムを使用して、コンテナ オブジェクトによって生成される可能性のある循環参照の問題を解決します。(リスト、辞書、ユーザー定義クラスのオブジェクト、タプルなどのコンテナ オブジェクトのみが循環参照を生成することに注意してください。数値や文字列などの単純な型には循環参照がありません。最適化戦略として、単純なオブジェクトのみを含むタプルは、タイプはマークアンドスイープ アルゴリズムでは考慮されません)

マーククリア アルゴリズムは主に潜在的な循環参照問題に使用され、アルゴリズムは 2 つのステップに分かれています。

* 标记阶段。将所有的对象看成图的节点,根据对象的引用关系构造图结构。从图的根节点遍历所有的对象,所有访问到的对象被打上标记,表明对象是“可达”的。
* 清除阶段。遍历所有对象,如果发现某个对象没有标记为“可达”,则就回收。

ここに画像の説明を挿入

オブジェクトは参照 (ポインタ) によってリンクされて有向グラフを形成し、オブジェクトはこの有向グラフのノードを構成し、参照関係はこの有向グラフのエッジを構成します。ルート オブジェクトから開始して、オブジェクトは有向エッジに沿って移動されます。到達可能なオブジェクトはアクティブ オブジェクトとしてマークされ、到達不可能なオブジェクトはクリアされる非アクティブ オブジェクトとなります。ルート オブジェクトは、グローバル変数、コール スタック、およびレジスタです。
上の図では、小さな黒丸をグローバル変数、つまりルート オブジェクトとみなします。小さな黒丸から開始すると、オブジェクト 1 に直接到達でき、マークされ、オブジェクト 2 と 3 はマークされます。マーク、および 4 と 5 が到達不能である場合、1、2、および 3 はアクティブなオブジェクト、4 と 5 は GC によってリサイクルされる非アクティブなオブジェクトになります。

以下の図に示すように、マークアンドクリア アルゴリズムでは、コンテナ オブジェクトを追跡するために、各コンテナ オブジェクトは、コンテナ オブジェクトの両端リンク リストを形成するために使用される 2 つの追加ポインタを維持する必要があります。オペレーション。Python インタープリター (Cpython) は、このような両端リンク リストを 2 つ維持します。1 つのリンク リストにはスキャンが必要なコンテナ オブジェクトが格納され、もう 1 つのリンク リストには一時的に到達できないオブジェクトが格納されます。この図では、2 つのリンクされたリストにはそれぞれ「スキャンするオブジェクト」と「到達不能」という名前が付けられています。図の例はそのような状況です。link1、link2、link3 は参照リングを形成し、link1 は変数 A (実際には、ここでは名前 A と呼ぶ方が適切です) によっても参照されます。link4 は自己参照であり、参照サイクルも構成します。この図から、各ノードには現在の参照カウントを記録する変数 ref_count に加えて、gc_ref 変数も存在することがわかりますが、この gc_ref は ref_count のコピーであるため、初期値は ref_count のサイズになります。

ここに画像の説明を挿入

gc が開始されると、「スキャンするオブジェクト」リスト内のコンテナー オブジェクトを 1 つずつ調べ、現在のオブジェクトによって参照されるすべてのオブジェクトの gc_ref を 1 つ減らします。(link1がスキャンされると、link1がlink2を参照しているため、link2のgc_refが1減って、その後link2がスキャンされます。link2がlink3を参照しているので、link3のgc_refが1減ります...) このように, 「スキャンするオブジェクト」リンク リスト内のすべてのオブジェクトを調べた後、2 つのリンク リスト内のオブジェクトの ref_count と gc_ref が次の図に示されます。このステップは、参照カウントに対する循環参照の影響を除去することと同じです。

ここに画像の説明を挿入

次に、gc はすべてのコンテナ オブジェクトを再度スキャンします。オブジェクトの gc_ref 値が 0 の場合、オブジェクトは GC_TENTATIVELY_UNREACHABLE としてマークされ、「到達不能」リストに移動されます。下図の Link3 と link4 がそのような状況です。

ここに画像の説明を挿入

オブジェクトの gc_ref が 0 でない場合、オブジェクトは GC_REACHABLE としてマークされます。同時に、gc はノードが到達可能であることを検出すると、このノードから到達可能なすべてのノードを再帰的に GC_REACHABLE としてマークします。これは、以下の図のリンク 2 とリンク 3 が遭遇する状況です。

ここに画像の説明を挿入

すべての到達可能なノードを GC_REACHABLE としてマークすることに加えて、ノードが現在「到達不能」リストにある場合は、「スキャンするオブジェクト」リストに戻す必要があります。次の図は、リンク 3 が戻された後の状況を示しています。

ここに画像の説明を挿入

2 番目の走査のすべてのオブジェクトが走査された後、「到達不能」リンク リストに存在するオブジェクトが、実際に解放する必要があるオブジェクトになります。上図に示すように、この時点では link4 が到達不能リンク リストに存在しており、gc はそれをすぐに解放します。

上で説明したガベージ コレクション フェーズでは、アプリケーション全体が一時停止され、マークがクリアされるのを待ってからアプリケーションの動作が再開されます。

3. 世代別コレクション

循環参照オブジェクトのリサイクルではアプリケーション全体が一時停止することになりますが、アプリケーションの一時停止時間を短縮するために、Python では「世代別コレクション」によって空間と時間を交換することでガベージ コレクションの効率を向上させています。

世代別リサイクルは、プログラムの特定の割合のメモリ ブロックのライフ サイクルが比較的短い一方、残りのメモリ ブロックのライフ サイクルは、プログラムの開始から終了まででも比較的長いという統計的事実に基づいています。プログラム。存続期間の短いオブジェクトの割合は、通常 80% から 90% の間です。これは単純に、オブジェクトが長く存在するほど、それがガベージではない可能性が高く、収集する必要がなくなるということです。このようにして、マーククリア アルゴリズムの実行時にトラバースされるオブジェクトの数を効果的に減らすことができ、それによってガベージ コレクションの速度が向上します。

Python gc では、オブジェクトに 3 つの世代 (0、1、2) が定義されています。すべての新しいオブジェクトは世代 0 にあります。GC スキャンのラウンドを生き残った場合は、世代 1 に移動され、スキャンの量が減ります。別の GC ラウンドを生き延びると、第 2 世代に移動され、スキャンの頻度が減ります。

gc スキャンはいつトリガーされますか? その答えは、特定の世代で割り当てられたオブジェクトと解放されたオブジェクトの差が特定のしきい値に達すると、特定の世代の gc スキャンがトリガーされるということです。ある世代のスキャンがトリガーされると、その世代よりも若い世代もスキャンされることに注意してください。つまり、世代 2 の gc スキャンがトリガーされると、世代 0 と世代 1 もスキャンされ、世代 1 の gc スキャンがトリガーされると、世代 0 もスキャンされます。

しきい値は、次の 2 つの機能を通じて表示および調整できます。

gc.get_threshold() # (threshold0, threshold1, threshold2).
gc.set_threshold(threshold0[, threshold1[, threshold2]])

以下では、set_threshold() の 3 つのパラメーターthreshold0、threshold1、threshold2 を紹介します。GC は、前回の収集以降に新しく割り当てられたオブジェクトの数と解放されたオブジェクトの数を記録します。この 2 つの差がしきい値 0 の値を超えると、GC スキャンが開始されます。最初は、世代 0 のみがチェックされます。世代 1 が最後にチェックされてから、世代 0 が Threshold1 回を超えてチェックされた場合、世代 1 のチェックがトリガーされます。同様に、世代 2 が最後にチェックされてから世代 1 が Threshold2 回以上チェックされている場合、世代 2 のチェックがトリガーされます。get_threshold() は 3 つの値を取得するもので、デフォルト値は (700,10,10) です。

まとめ
一般に、Python では、ガベージ コレクションは主に参照カウント、コンテナ オブジェクトによって生成される循環参照の問題を解決する「マーククリア」、ガベージ コレクションの効率を向上させる「世代別コレクション」によって実行されます。空間を時間と交換することによって。

おすすめ

転載: blog.csdn.net/TFATS/article/details/129859219