py

楼主: sustainer123 (caster)   2024-11-12 16:41:44
姆咪 这个好麻烦 不过比ml文章好写
作为一个高阶语言,python拥有自动GC的机制
在本文中,我们将讨论python最常用的编译器:Cpython的GC
引用记数机制是Cpython GC的重要环节
首先 我们将聚焦于引用计数机制
引用计数机制
typedef struct _object {
_PyObject_HEAD_EXTRA
Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;
} PyObject;
Cpython定义的物件长成上面那样子
ob_refcnt就是引用计数
引用计数的概念并不复杂
当我们创建一个物件,该物件的引用计数就加一
反之,删除一个物件,引用计数减一
import sys
a = 1
print(sys.getrefcount(1))
del a
print(sys.getrefcount(1))
这时候output会是一串很长的数字(1000000052)
直观上,我们会预期out会是1跟0
最多基于某些隐式引用,output可能会多一点
但不该是一串很长的数字
之所以会跑出一串很长的数字
这是因为某些小整数经常被使用,为了避免不断为这些小整数分配与释出内存
程式开始执行时,Cpython预先为这些整数分配内存并创建物件
这些小整数已经被多次引用,所以引用计数才会特别高
小整数的范围是-5到256(这是Cpython设定的范围,随着编译器不同而改变。)
import sys
a = 999
print(sys.getrefcount(999))
del a
print(sys.getrefcount(999))
改成999 ,这时候output就4与3,之所以不是1与0,这是因为隐式引用
诸如sys.getrefcount()本身就有一次临时引用
Cpyhton为了最佳化,编译时会把变量存入co_consts,这时引用数又加一。
假如引用记数归零,Cpython会销毁该物件,并释放内存。
...
#ifndef PyFloat_MAXFREELIST
# define PyFloat_MAXFREELIST 100
#endif
...
PyTypeObject PyFloat_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"float",
sizeof(PyFloatObject),
0,
(destructor)float_dealloc, /* tp_dealloc */
...
};
...
static void
float_dealloc(PyFloatObject *op)
{
#if PyFloat_MAXFREELIST > 0
if (PyFloat_CheckExact(op)) {
struct _Py_float_state *state = get_float_state();
#ifdef Py_DEBUG
// float_dealloc() must not be called after _PyFloat_Fini()
assert(state->numfree != -1);
#endif
if (state->numfree >= PyFloat_MAXFREELIST) {
PyObject_Free(op);
return;
}
state->numfree++;
Py_SET_TYPE(op, (PyTypeObject *)state->free_list);
state->free_list = op;
}
else
#endif
{
Py_TYPE(op)->tp_free((PyObject *)op);
}
}
前面主要聚焦于整数的引用计数与回收,现在让我们研究浮点数的回收机制
当浮点数的引用计数归零,这时会有两种可能
假若浮点数的freelist <100,浮点数放入freelist
假设freelist >= 100,则销毁浮点数并释放空间
Cpython设置free list的用意也是为了避免频繁地内存分配与释放
引用计数的问题
import gc
a = []
a.append(a)
del a
print(gc.collect())
这边发生循环引用,首先创建a,引用计数+=1
在 a.append(a) 之后,a 这个列表对象内部有一个元素是自己,这样 a 的引用计数变
成 2。
后面del a,引用计数-=1,我们预期a会被引用记数回收,但此时a的引用计数是1,因为a
包含对自己的引用,所以a不会被回收。
gc.collect()是手动触发其他回收机制,因为引用计数无法清理a,其他机制能解决循环
引用的问题,所以a被其他机制清除。gc.collect()回传1,代表一个物件被清除。
所以,单单依靠引用计数会出现上述的麻烦,这时候就有了其他配套机制,
参考文章:
https://hackmd.io/@8thEdition/cpython_garbage_collection_reference_counting

Links booklink

Contact Us: admin [ a t ] ucptt.com