PythonのGCモジュールは、主に「参照カウント」を使用してゴミを追跡および収集します。参照カウントに基づいて、マークとスイープを使用して、コンテナーオブジェクトによって生成される循環参照の問題を解決することもできます。スペースと引き換えに「ジェネレーションコレクション」(ジェネレーションコレクション)を行い、ガベージコレクションの効率をさらに向上させます。
参照カウントメカニズム:
Pythonのすべてがオブジェクトであり、そのコアは構造です:PyObject
typedef struct_object { int ob_refcnt; struct_typeobject * ob_type; } PyObject;
PyObjectは各オブジェクトに必須のコンテンツであり、ob_refcntが参照カウントとして使用されます。オブジェクトに新しい参照がある場合、そのob_refcntは増加し、それを参照するオブジェクトが削除されると、そのob_refcntは減少します
#define Py_INCREF(op)((op)-> ob_refcnt ++)//カウントを増やす #define Py_DECREF(op)\ //カウントを減らす if(-(op)-> ob_refcnt!= 0)\ ; \ そうしないと \ __Py_Dealloc((PyObject *)(op))
参照カウントが0の場合、オブジェクトの寿命は終了しています。
参照カウントメカニズムの利点:
1.シンプル
2.リアルタイム:参照がなくなると、メモリは直接解放されます。他のメカニズムのように特定の時間まで待つ必要はありません。リアルタイムにもメリットがあります。回収されたメモリを処理する時間が通常の時間に割り当てられます。
参照カウントメカニズムの短所:
1.参照カウントを維持するとリソースが消費されます
2.循環参照
list1 = [] list2 = [] list1.append(list2) list2.append(list1)
List1とList2は相互に参照します。他のオブジェクトがそれらを参照していない場合、list1とlist2の参照カウントは1のままであり、占有メモリは再利用できません。これは致命的です。
今日の強力なハードウェアでは、欠点1はまだ許容されますが、循環参照はメモリリークを引き起こし、運命のPythonも新しいリサイクルメカニズムを導入します。
上記のように、Pythonのリサイクルメカニズムは主に参照カウントに基づいており、マークスイープと世代別コレクションの2つのメカニズムが補足されています。
1.マーククリアメカニズム
マークアンドスイープメカニズムは、その名前が示すように、最初にオブジェクトにマークを付け(ガベージ検出)、次にガベージをクリアします(ガベージコレクション)。示されているように:
最初に、すべてのオブジェクトが最初に白としてマークされ、ルートノードオブジェクトが決定され(これらのオブジェクトは削除されません)、黒としてマークされます(オブジェクトが有効であることを示します)。有効なオブジェクトによって参照されるオブジェクトを灰色としてマークし(オブジェクトは到達可能であるが、それらが参照するオブジェクトはチェックされていないことを示します)、灰色のオブジェクトによって参照されるオブジェクトをチェックした後、灰色を黒としてマークします。灰色のノードがなくなるまで繰り返します。最後に、白いノードはすべてクリアする必要があるオブジェクトです。
2.リサイクル対象物の整理
ここで採用されている高レベルのメカニズムは、循環参照の問題を解決するための参照カウントの補助メカニズムとして使用されます。循環参照は、リスト、クラスなどの「他のオブジェクトから参照できるオブジェクトがあります」にのみ表示されます。
これらのリサイクルオブジェクトを整理するには、リンクリストを作成する必要があります。当然のことながら、収集された各オブジェクトは、さらにいくつかの情報を提供する必要があります。次のコードは、リサイクルオブジェクトで不可避です。
/ * GC情報はオブジェクト構造の前に保存されます。* / typedef union _gc_head { 構造体{ union _gc_head * gc_next; union _gc_head * gc_prev; Py_ssize_t gc_refs; } gc; 長い二重ダミー; / *最悪の場合の整列を強制する* / } PyGC_Head;
オブジェクトの実際の構造を図に示します。
PyGC_Headを通るポインタは、再利用された各オブジェクトを接続して、リンクリスト、つまり、1で説明した初期化されたすべてのオブジェクトを形成します。
3.世代別テクノロジー
世代別テクノロジーは、典型的な時間までのテクノロジーであり、Javaの主要テクノロジーでもあります。単純な考え方は、オブジェクトが長く存在するほど、ゴミにならない可能性が高くなり、収集する必要が少なくなるということです。
このアイデアは、マークアンドスイープメカニズムによってもたらされる追加の操作を減らすことができます。世代とは、リサイクルオブジェクトを複数の世代に分割することで、各世代はリンクされたリスト(コレクション)であり、世代マーククリアと世代オブジェクトの時間です
生存時間は比例します
/ ***グローバルGC状態*** / struct gc_generation { PyGC_Headヘッド; intしきい値。/ *コレクションしきい値* / int count; / *より若いの割り当てまたはコレクションの数 世代* / }; //各世代の構造 #define NUM_GENERATIONS 3 //世代数 #define GEN_HEAD(n)(&generations [n] .head) / *コンテナオブジェクトのリンクリスト* / static struct gc_generation generations [NUM_GENERATIONS] = { / * PyGC_Head、しきい値、カウント* / {{{GEN_HEAD(0)、GEN_HEAD(0)、0}}、700、0}、 {{{GEN_HEAD(1)、GEN_HEAD(1)、0}}、10、0}、 {{{GEN_HEAD(2)、GEN_HEAD(2)、0}}、10、0}、 }; PyGC_Head * _PyGC_generation0 = GEN_HEAD(0);
上記のコードからわかるように、Pythonには3つの世代があり、各世代のしきい値は、その世代が対応できるオブジェクトの最大数を示しています。デフォルトでは、ジェネレーション0が700を超えるか、ジェネレーション1、2が10を超えると、ガベージコレクションメカニズムがトリガーされます。
0世代のトリガーは3つの世代すべてをクリーンアップし、1世代のトリガーは1、2世代をクリーンアップし、2世代のトリガーはそれ自体をクリーンアップするだけです。
これは完全な収集プロセスです:リンクリストの作成、ルートノードの決定、ガベージマーキング、ガベージコレクション〜
1.リンクリストの確立
まず、Zhongliは世代別テクノロジーで次のように述べています。0世代トリガーは3世代すべてをクリーンアップし、1世代トリガーは1、2世代をクリーンアップし、2世代は自分自身のみをクリーンアップします。ジェネレーション0をクリーンアップすると、3つのリンクリスト(ジェネレーション)がリンクされ、ジェネレーション1をクリーンアップすると、2つまたは2つのジェネレーションがリンクされます。次の3つのステップはすべて、設立後のリンクリストに向けられています。
2.ルートノードを決定する
図1は例です。List1とList2は循環参照され、List3とList4は循環参照されます。aは外部参照です。
このようなリンクリストの場合、ルートノードを取得するにはどうすればよいですか。Pythonでは、効果的な参照カウントの概念が参照カウントに基づいて提案されています。名前が示すように、有効な参照カウントは循環参照を削除した後のカウントです。
有効な参照カウントを計算するための関連コードは次のとおりです。
/ *すべてのgc_refs = ob_refcntを設定します。この後、すべてのオブジェクトでgc_refsは> 0になります。 *コンテナー内にあり、追跡されていないすべてのgcオブジェクトに対してGC_REACHABLEです。 *コンテナ。 * / 静的ボイド update_refs(PyGC_Head * containers) { PyGC_Head * gc =コンテナ-> gc.gc_next; for(; gc!=コンテナ; gc = gc-> gc.gc_next){ assert(gc-> gc.gc_refs == GC_REACHABLE); gc-> gc.gc_refs = Py_REFCNT(FROM_GC(gc)); assert(gc-> gc.gc_refs!= 0); } } / * subtract_refsのトラバーサルコールバック。* / 静的整数 visit_decref(PyObject * op、void * data) { アサート(op!= NULL); if(PyObject_IS_GC(op)){ PyGC_Head * gc = AS_GC(op); / *関心のあるオブジェクトのgc_refsのみ *認識できる世代が収集されています *正のgc_refを持っているため。 * / assert(gc-> gc.gc_refs!= 0); / *それ以外の参照カウントが小さすぎます* / if(gc-> gc.gc_refs> 0) gc-> gc.gc_refs--; } 0を返します。 } / * gc_refsから内部参照を差し引きます。この後、gc_refsは> = 0 *コンテナ内のすべてのオブジェクトに対して、追跡されたすべてのgcに対してGC_REACHABLE *コンテナにないオブジェクト。gc_refs> 0のものが直接 ※外部コンテナからのアクセスが可能なため、回収できません。 * / 静的ボイド subtract_refs(PyGC_Head * containers) { traverseprocトラバース。 PyGC_Head * gc =コンテナ-> gc.gc_next; for(; gc!=コンテナ; gc = gc-> gc.gc_next){ トラバース= Py_TYPE(FROM_GC(gc))-> tp_traverse; (void)トラバース(FROM_GC(gc)、 (visitproc)visit_decref、 ヌル); } }
参照のコピーがupdate_refs関数で作成されます。
visit_decref関数は、参照のコピーを1だけデクリメントします。subtract_refs関数のトラバース機能は、オブジェクト内の各参照をトラバースし、visit_decref操作を実行することです。
最後に、リンクされたリストで参照カウントのコピーが0でないオブジェクトはルートノードです。
説明:
1.なぜ参照コピーを作成するのですか?
回答:このプロセスはルートノードを見つけるプロセスですが、現時点では、カウントを変更することは適切ではありません。subtract_refsは、オブジェクトの参照オブジェクトに対してvisit_decref操作を実行します。リンクリスト内のオブジェクトがリンクリスト外のオブジェクトを参照している場合、リンクリスト外のオブジェクト数は減少します。明らかに、このオブジェクトはリサイクルされる可能性が非常に高く、リサイクルメカニズムは非リサイクルオブジェクトをまったく処理するべきではありません。
2.トラバースの質問(未解決)?
回答:最初に質問がありました。上記の例では、subtract_refs関数でlist1を処理した結果は次のようになります。
次に、gcはlist2をポイントします。現時点では、list2(0)のコピーは減少しませんが、list1への実際の参照はまだあるため、list1のコピーは1だけ減りますか?明らかに、マイナス1の場合は問題があります。
したがって、list1が0の場合、トラバースはlist1の参照をまったく処理しません(または、list2にはlist1への名目上の参照がありません)。
このとき、次のようにsubtract_refs関数でlist1を処理した後、list2への参照である外部オブジェクトbがある場合、別の問題があります。
list_refs関数がlist2に移動すると、list2のコピーは1つ減りますか?明らかに、トラバースの役割はまだ理解されていません。
3.ガベージマーキング
次に、Pythonは2つのリンクリストを作成します。1つはルートノードとルートノードの参照オブジェクトを格納します。もう1つは到達できないオブジェクトを格納します。
マーキングの方法は、中レキでのマーキングのアイデアで、コードは次のとおりです。
/ * move_unreachableのトラバーサルコールバック。* / 静的整数 visit_reachable(PyObject * op、PyGC_Head * reachable) { if(PyObject_IS_GC(op)){ PyGC_Head * gc = AS_GC(op); const Py_ssize_t gc_refs = gc-> gc.gc_refs; if(gc_refs == 0){ / *これはmove_unreachableの 'young'リストにありますが、 *トラバーサルはまだ行われていません。すべて *必要なことは、move_unreachableに *到達可能。 * / gc-> gc.gc_refs = 1; } else if(gc_refs == GC_TENTATIVELY_UNREACHABLE){ / *これは、move_unreachableが取得したときにgc_refs = 0でした *それまでですが、結局到達可能です。 * move_unreachableの「young」リストに戻します。 *そしてmove_unreachableは最終的にそれに到達します *再び。 * / gc_list_move(gc、到達可能); gc-> gc.gc_refs = 1; } / *そうでなければ、何もする必要はありません。 * gc_refs> 0の場合、move_unreachableの「young」にある必要があります *リスト、およびmove_unreachableは最終的にそれに到達します。 * gc_refs == GC_REACHABLEの場合、他のいずれかにあります *世代は気にしないので、またはmove_unreachable ※既に対応済みです。 * gc_refs == GC_UNTRACKEDの場合、無視する必要があります。 * / そうしないと { assert(gc_refs> 0 || gc_refs == GC_REACHABLE || gc_refs == GC_UNTRACKED); } } 0を返します。 } / *到達不能オブジェクトを若いオブジェクトから到達不能オブジェクトに移動します。この後、 * youngのすべてのオブジェクトにはgc_refs = GC_REACHABLEがあり、 *到達不可能である場合、gc_refs = GC_TENTATIVELY_UNREACHABLEになります。すべて追跡 *新しくない、または到達できないgcオブジェクトには、まだgc_refs = GC_REACHABLEがあります。 *この後の若いオブジェクトはすべて直接または間接的に到達可能です *元の若者の外から。到達不能のすべてのオブジェクトは *ありません。 * / 静的ボイド move_unreachable(PyGC_Head * young、PyGC_Head * unreachable) { PyGC_Head * gc = young-> gc.gc_next; / *不変式:若い私たちの「左側」のすべてのオブジェクトはgc_refsを持っています * = GC_REACHABLE、実際に到達可能(直接または間接) *エントリー時と同じように、若いリストの外側から。他のすべてのオブジェクト *私たちの元の若い「左側」からは、現在到達不可能です、 *そしてgc_refs = GC_TENTATIVELY_UNREACHABLEがあります。へのすべてのオブジェクト *「若い」の私たちの左側がスキャンされました。ここにはオブジェクトはありません *または右側がまだスキャンされています。 * / while(gc!= young){ PyGC_Head *次へ; if(gc-> gc.gc_refs){ / * gcは間違いなく外部から到達可能です *オリジナルの「ヤング」。そのようにマークし、トラバースする *他のオブジェクトを見つけるためのポインタ *そこから直接到達できる。注意してください * tp_traverseを呼び出すと、オブジェクトがyoungに追加される場合があります。 *したがって、決定するために戻るまで待つ必要があります *次に訪問するオブジェクト。 * / PyObject * op = FROM_GC(gc); traverseproc traverse = Py_TYPE(op)-> tp_traverse; assert(gc-> gc.gc_refs> 0); gc-> gc.gc_refs = GC_REACHABLE; (void)トラバース(オン、 (visitproc)visit_reachable、 (ボイド*)若い); 次= gc-> gc.gc_next; } そうしないと { / *これは*到達不可能かもしれません*。進歩するために、 *あると想定します。gcから直接到達できない *すでに通過したオブジェクトですが、 *まだ到達していないオブジェクトから到達可能。 * visit_reachableは最終的にgcをに戻します *そうであれば若く、また見ます。 * / 次= gc-> gc.gc_next; gc_list_move(gc、unreachable); gc-> gc.gc_refs = GC_TENTATIVELY_UNREACHABLE; } gc =次; } }
マークした後、リンクされたリストは上記のようになります。
4.ガベージコレクション
のプロセスは、到達不能なリンクリスト内のオブジェクトを破棄することです。次のコードは、リストをクリアする方法です。
/ *メソッド* / 静的ボイド list_dealloc(PyListObject * op) { Py_ssize_t i; PyObject_GC_UnTrack(op); Py_TRASHCAN_SAFE_BEGIN(op) if(op-> ob_item!= NULL){ / *クリスチャン・ティズマーのために、逆にそれをしなさい。 これが何らかの形で減少する単純なテストケースがあります *非常に*大規模なリストが作成され、 すぐに削除されました。* / i = Py_SIZE(op); while(--i> = 0){ Py_XDECREF(op-> ob_item [i]); } PyMem_FREE(op-> ob_item); } if(numfree <PyList_MAXFREELIST && PyList_CheckExact(op)) free_list [numfree ++] = op; そうしないと Py_TYPE(op)-> tp_free((PyObject *)op); Py_TRASHCAN_SAFE_END(op) }
Python学習ロードマップhttps://www.bilibili.com/video/BV1V741117Zt/
Pythonの雇用の方向性とキャリアの見通しhttps://www.bilibili.com/video/BV1ut4y1U7bM/