python源碼閱讀筆記之GC
引用計數法
增量:
各個對象的內部都有計數器。如果對象的引用數量增加,就在計數器上加1,否則減1
#define Py_INCREF(op) (
_Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA
((PyObject*)(op))->ob_refcnt++)
下面的是含有NULL檢查的宏
#define Py_XINCREF(op) do { if ((op) == NULL) ; else Py_INCREF(op); } while (0)
計數器如何避免溢出?
#define PyObject_HEAD
_PyObject_HEAD_EXTRA
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
從這裡可以看出ob_refcnt的數據類型
在pyport.h
#ifdef HAVE_SSIZE_T
typedef ssize_t Py_ssize_t;
#elif SIZEOF_VOID_P == SIZEOF_SIZE_T
typedef Py_intptr_t Py_ssize_t;
#else
# error "Python needs a typedef for Py_ssize_t in pyport.h."
#endif
是C里的ssize_t類型,這樣就可以和各自CPU位數的指針的大小一樣。因為有符號位,所以只有一半的數值能用非負整數表示,為啥計數器要使用負數呢?這是為了debug
#define _Py_CHECK_REFCNT(OP)
{ if (((PyObject*)OP)->ob_refcnt < 0)
_Py_NegativeRefcount(__FILE__, __LINE__,
(PyObject *)(OP));
}
減量操作
#define Py_DECREF(op)
do {
if (_Py_DEC_REFTOTAL _Py_REF_DEBUG_COMMA
--((PyObject*)(op))->ob_refcnt != 0)
_Py_CHECK_REFCNT(op)
else
_Py_Dealloc((PyObject *)(op));
} while (0)
先將計數器減量,如果得出0以外的數值就調用_Py_CHECK_REFCNT(),為了防止意外
否則調用_Py_Dealloc()
#define _Py_Dealloc(op) (
_Py_INC_TPFREES(op) _Py_COUNT_ALLOCS_COMMA
(*Py_TYPE(op)->tp_dealloc)((PyObject *)(op)))
tp_dealloc存儲著釋放各個對象的函數指針
大概的釋放的調用如此
Py_DECREF
_Py_Dealloc
tupledealloc
PyObject_GC_Del
PyObject_FREE
PyObject_Free
插入計數處理
這些技術都是基於生成指向對象的引用時進行的,而這不一定是Python的對象
局部變數引用時,絕大多數情況都不用引用計數
python終結器的概念:
內置數據類型的對象是不能設置終結器的,能定義終結器的只有用戶創建的類
循環引用垃圾回收:
容器對象:可能保留了指向其他對象的引用的對象
這些對象都分配的用於循環引用垃圾回收的頭結構體
/* GC information is stored BEFORE the object structure. */
typedef union _gc_head {
struct {
union _gc_head *gc_next;
union _gc_head *gc_prev;
Py_ssize_t gc_refs;
} gc;
long double dummy; /* force worst-case alignment */
} PyGC_Head;
前兩個用於雙向鏈表,最後一個是用於複製
dummy的作用如源碼注釋:即使結構體gc的大小不合理。它也會將整個結構體PyGC_Head的大小對齊為long double型
由下述代碼負責分配所有容器對象的函數
PyObject *
_PyObject_GC_Malloc(size_t basicsize)
{
PyObject *op;
PyGC_Head *g;
if (basicsize > PY_SSIZE_T_MAX - sizeof(PyGC_Head))
return PyErr_NoMemory();
g = (PyGC_Head *)PyObject_MALLOC(
sizeof(PyGC_Head) + basicsize);/*此處可見,在分配對象時也額外分配了用於循環引用垃圾回收的頭大小*/
if (g == NULL)
return PyErr_NoMemory();
g->gc.gc_refs = GC_UNTRACKED;/*GC_UNTRACKED存入用於循環引用垃圾回收的頭內成員gc_refs,當出現這個標誌時,GC會認為這個容器對象沒有連接到對象鏈*/
generations[0].count++; /* number of allocated GC objects */
if (generations[0].count > generations[0].threshold &&
enabled &&
generations[0].threshold &&
!collecting &&
!PyErr_Occurred()) {
collecting = 1;
collect_generations();
collecting = 0;
}
op = FROM_GC(g);/*這個會偏移用於循環垃圾引用的頭的長度,返回正確的對象地址,為了不區分容器對象和其他對象*/
return op;
}
#define GC_UNTRACKED _PyGC_REFS_UNTRACKED
gc_refs用負值做標誌,這是為了節省空間
/* Get the object given the GC head */
#define FROM_GC(g) ((PyObject *)(((PyGC_Head *)g)+1))
追蹤容器對象
這個宏放在各個容器對象新建立的時候,負責連接鏈表的操作
/* Tell the GC to track this object. NB: While the object is tracked the
* collector it must be safe to call the ob_traverse method. */
#define _PyObject_GC_TRACK(o) do {
PyGC_Head *g = _Py_AS_GC(o); *先從對象的開頭地址開始,將頭地址偏移相應的大小,取出用於循環引用垃圾回收的頭*
if (g->gc.gc_refs != _PyGC_REFS_UNTRACKED)
Py_FatalError("GC object already tracked");
g->gc.gc_refs = _PyGC_REFS_REACHABLE; *修改gc_refs,表示可到達*
g->gc.gc_next = _PyGC_generation0; *拿出了連接所有容器對象的全局性容器對象鏈表,把對象鏈接到這個鏈表*
g->gc.gc_prev = _PyGC_generation0->gc.gc_prev;
g->gc.gc_prev->gc.gc_next = g;
_PyGC_generation0->gc.gc_prev = g;
} while (0);
#define _Py_AS_GC(o) ((PyGC_Head *)(o)-1)
同樣的方式可以結束追蹤對象;
/*** Global GC state ***/
struct gc_generation {
PyGC_Head head;
int threshold; /* collection threshold */
int count; /* count of allocations or collections of younger
generations */
};
這個結構體用於管理各代的容器對象,一旦count超過了threshold程序就會對這一代執行GC
0代 生成的容器對象的數量 - 刪除的容器對象的數量
1代 0代經過GC的次數
2代 1代經過GC的次數
/* linked lists of container objects */
static struct gc_generation generations[NUM_GENERATIONS] = {
/* PyGC_Head, threshold, count */
{{}, 700, 0},
{{}, 10, 0},
{{}, 10, 0},
};
PyGC_Head *_PyGC_generation0 = GEN_HEAD(0);
一開始的所有容器對象都連接著0代的對象
編輯 | 碼哥
圖片源於網路,版權歸原作者所有


※印度diss中國?真正的威脅到底是什麼?
※開除程序員 搞了個喪文案?阿里中秋「搞大事情」
※清華竟然不敵交大!全球大學計算機專業排名!
※微軟新里程碑!SQL Server 首次登陸 Linux 平台
※馬雲又雙叒叕搞事情啦!淘寶怎麼活!
TAG:程序員之家 |