翼度科技»论坛 编程开发 python 查看内容

Python垃圾回收

7

主题

7

帖子

21

积分

新手上路

Rank: 1

积分
21
Python版本

v3.9.17
分析代码的过程比较枯燥,可以直接跳转到总结。
只能被其他对象引用类型

比如:longobject、floatobject
floatobject

以floatobject为例子来分析,先看看结构定义
  1. typedef struct {
  2.     PyObject_HEAD
  3.     double ob_fval;
  4. } PyFloatObject;
  5. // 展开PyObject_HEAD后
  6. typedef struct {
  7.     PyObject ob_base;
  8.     double ob_fval;
  9. } PyFloatObject;
  10. typedef struct _object {
  11.     _PyObject_HEAD_EXTRA
  12.     Py_ssize_t ob_refcnt;
  13.     PyTypeObject *ob_type;
  14. } PyObject;
复制代码
在PyObject中的_PyObject_HEAD_EXTRA,只有在编译时指定--with-trace-refs才有效,这里忽略即可。
  1. ./configure --with-trace-refs
复制代码
可以看到在PyObject里有一个ob_refcnt的属性,这个就是引用计数。
当对引用计数减为0时,就会调用各类型对应的析构函数。
  1. define Py_DECREF(op) _Py_DECREF(_PyObject_CAST(op))
  2. void _Py_Dealloc(PyObject *op)
  3. {
  4.     destructor dealloc = Py_TYPE(op)->tp_dealloc;
  5.     (*dealloc)(op);
  6. }
  7. static inline void _Py_DECREF(PyObject *op)
  8. {
  9.     if (--op->ob_refcnt != 0) {
  10.     }
  11.     else {
  12.         _Py_Dealloc(op);
  13.     }
  14. }
复制代码
能引用其他对象的类型

比如listobject,dictobject...
listobject

以listobject为例子来分析,先看看结构定义
  1. typedef struct {
  2.     PyObject_VAR_HEAD
  3.     PyObject **ob_item;
  4.     Py_ssize_t allocated;
  5. } PyListObject;
  6. // 展开 PyObject_VAR_HEAD
  7. typedef struct {
  8.     PyVarObject ob_base;
  9.     PyObject **ob_item;
  10.     Py_ssize_t allocated;
  11. } PyListObject;
  12. typedef struct {
  13.     PyObject ob_base;
  14.     Py_ssize_t ob_size; /* Number of items in variable part */
  15. } PyVarObject;
复制代码
可以看出,PyObject_VAR_HEAD也就比PyObject_HEAD多了一个Py_ssize_t ob_size而已,这个属性是用来表示这个可变对象里元素数量。
因为可以引用其他对象,就有可能会出现环引用问题,这种问题如果再使用引用计数来作为GC就会出现问题。
  1. lst1 = []
  2. lst2 = []
  3. lst1.append(lst2)
  4. lst2.append(lst1)
复制代码
当然这种情况可以使用弱引用,或者手动解除环引用。这些解决方案这里不深入,现在主要看看python是怎样应对这种情况。
对于这类型的对象在申请内存的时候调用的是PyObject_GC_New,而不可变类型是用PyObject_MALLOC。为了减少篇幅,删掉了一些判断逻辑。
  1. typedef struct {
  2.     // Pointer to next object in the list.
  3.     // 0 means the object is not tracked
  4.     uintptr_t _gc_next;
  5.     // Pointer to previous object in the list.
  6.     // Lowest two bits are used for flags documented later.
  7.     uintptr_t _gc_prev;
  8. } PyGC_Head;
  9. #define FROM_GC(g) ((PyObject *)(((PyGC_Head *)g)+1))
  10. static PyObject * _PyObject_GC_Alloc(int use_calloc, size_t basicsize)
  11. {
  12.     PyThreadState *tstate = _PyThreadState_GET();
  13.     GCState *gcstate = &tstate->interp->gc;
  14.     size_t size = sizeof(PyGC_Head) + basicsize;
  15.     PyGC_Head *g;
  16.     g = (PyGC_Head *)PyObject_Malloc(size);
  17.     g->_gc_next = 0;
  18.     g->_gc_prev = 0;
  19.     gcstate->generations[0].count++; /* number of allocated GC objects */
  20.     if (/* 判断是否可以执行GC */)
  21.     {
  22.         gcstate->collecting = 1;
  23.         collect_generations(tstate);
  24.         gcstate->collecting = 0;
  25.     }
  26.     PyObject *op = FROM_GC(g);
  27.     return op;
  28. }
复制代码
在可变对象中,python又加上了一个PyGC_Head。通过这个PyGC_Head将listobject链接到gc列表中。
在分配完listobject内存后,紧接着调用_PyObject_GC_TRACK,链接到gc列表中。
  1. static inline void _PyObject_GC_TRACK_impl(const char *filename, int lineno,
  2.                                            PyObject *op)
  3. {
  4.     PyGC_Head *gc = _Py_AS_GC(op);
  5.     PyThreadState *tstate = _PyThreadState_GET();
  6.     PyGC_Head *generation0 = tstate->interp->gc.generation0;
  7.     PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev);
  8.     _PyGCHead_SET_NEXT(last, gc);
  9.     _PyGCHead_SET_PREV(gc, last);
  10.     _PyGCHead_SET_NEXT(gc, generation0);
  11.     generation0->_gc_prev = (uintptr_t)gc;
  12. }
复制代码
通过这里的变量名,可以猜测使用到了分代垃圾回收。
分代回收

python手动执行垃圾回收一般调用gc.collect(generation=2)函数。
  1. #define NUM_GENERATIONS 3
  2. #define GC_COLLECT_METHODDEF    \
  3.     {"collect", (PyCFunction)(void(*)(void))gc_collect, METH_FASTCALL|METH_KEYWORDS, gc_collect__doc__},
  4. static PyObject *
  5. gc_collect(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
  6. {
  7.     PyObject *return_value = NULL;
  8.     int generation = NUM_GENERATIONS - 1;
  9.     Py_ssize_t _return_value;
  10.     _return_value = gc_collect_impl(module, generation);
  11.     if ((_return_value == -1) && PyErr_Occurred()) {
  12.         goto exit;
  13.     }
  14.     return_value = PyLong_FromSsize_t(_return_value);
  15. exit:
  16.     return return_value;
  17. }
复制代码
具体执行在gc_collect_impl函数中,接着往下
  1. static Py_ssize_t gc_collect_impl(PyObject *module, int generation)
  2. {
  3.     PyThreadState *tstate = _PyThreadState_GET();
  4.     GCState *gcstate = &tstate->interp->gc;
  5.     Py_ssize_t n;
  6.     if (gcstate->collecting) {
  7.         /* already collecting, don't do anything */
  8.         n = 0;
  9.     }
  10.     else {
  11.         gcstate->collecting = 1;
  12.         n = collect_with_callback(tstate, generation);
  13.         gcstate->collecting = 0;
  14.     }
  15.     return n;
  16. }
复制代码
可以看到,如果已经在执行GC,则直接返回。接着看collect_with_callback
  1. static Py_ssize_t
  2. collect_with_callback(PyThreadState *tstate, int generation)
  3. {
  4.     assert(!_PyErr_Occurred(tstate));
  5.     Py_ssize_t result, collected, uncollectable;
  6.     invoke_gc_callback(tstate, "start", generation, 0, 0);
  7.     result = collect(tstate, generation, &collected, &uncollectable, 0);
  8.     invoke_gc_callback(tstate, "stop", generation, collected, uncollectable);
  9.     assert(!_PyErr_Occurred(tstate));
  10.     return result;
  11. }
复制代码
其中invoke_gc_callback是调用通过gc.callbacks注册的回调函数,这里我们忽略,重点分析collect函数。
collect函数签名
这段代码很长,我们拆分开来分析,这里会去除掉一些DEBUG相关的逻辑。
  1. static Py_ssize_t collect(PyThreadState *tstate, int generation,Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable, int nofail);
复制代码

  • 将新生代的对象合并到指定代的对象列表中。
  1. /* merge younger generations with one we are currently collecting */
  2. for (i = 0; i < generation; i++) {
  3.     gc_list_merge(GEN_HEAD(gcstate, i), GEN_HEAD(gcstate, generation));
  4. }
复制代码
比如调用gc.collect(2),就表示启动全部的垃圾回收。这里就会将第0、1代的对象合并到第2代上。合并之后第0、1代上就空了,全部可GC的对象都在第2代上。

  • 推断不可达对象
  1. /* handy references */
  2. young = GEN_HEAD(gcstate, generation);
  3. if (generation < NUM_GENERATIONS-1)
  4.     old = GEN_HEAD(gcstate, generation+1);
  5. else
  6.     old = young;
  7. validate_list(old, collecting_clear_unreachable_clear);
  8. deduce_unreachable(young, &unreachable);
复制代码
这里的young指针指向第2代的链表头,validate_list做校验,这里忽略,重点在deduce_unreachable函数中。
  1. static inline void
  2. deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable) {
  3.     validate_list(base, collecting_clear_unreachable_clear);
  4.     update_refs(base);  // gc_prev is used for gc_refs
  5.     subtract_refs(base);
  6.     gc_list_init(unreachable);
  7.     move_unreachable(base, unreachable);  // gc_prev is pointer again
  8.     validate_list(base, collecting_clear_unreachable_clear);
  9.     validate_list(unreachable, collecting_set_unreachable_set);
  10. }
复制代码
首先调用update_refs更新引用计数
[code]static inline voidgc_reset_refs(PyGC_Head *g, Py_ssize_t refs){    g->_gc_prev = (g->_gc_prev & _PyGC_PREV_MASK_FINALIZED)        | PREV_MASK_COLLECTING        | ((uintptr_t)(refs)

举报 回复 使用道具