原文链接:【Hard Python】【第三章-GC】1、引证计数与内存开释机制
关于编程言语runtime来说,建立起良好运转GC机制是非常必要的,像Java
和Go
,其GC机制都经历了复杂的演化,当然一起也为编程言语带来了更好的性能,这也是为什么这两门言语能成为干流服务端言语的原因之一。
相关于Java
和Go
,python
的GC机制是相对精约的,其间最基础的机制之一就是引证计数。当目标生成时引证计数为1;目标被其它目标引证时引证计数添加1;目标没有被引证,又退出效果域的话,引证计数归0;引证计数归0后,目标被毁掉。
我们能够经过一个比如对引证计数机制进行研究:
def test_ref():
a = '123456789123456789'
del a
其反编译的结果是:
8 0 LOAD_CONST 1 ('123456789123456789')
2 STORE_FAST 0 (a)
9 4 DELETE_FAST 0 (a)
6 LOAD_CONST 0 (None)
8 RETURN_VALUE
在STORE_FAST
和DELETE_FAST
操作中,都用到了SET_LOCAL
宏:
#define SETLOCAL(i, value) do { PyObject *tmp = GETLOCAL(i); \
GETLOCAL(i) = value; \
Py_XDECREF(tmp); } while (0)
能够看到SET_LOCAL
的操作是:将对应LOCAL
方位的旧值拷贝到tmp
指针,让后将新值赋给对应LOCAL
方位,最后削减旧值tmp
指针的引证计数
关于DELETE_FAST
操作,SETLOCAL
的value
参数是NULL
,这样对应LOCAL
方位指针被赋值为NULL
,旧值削减引证计数,这样就触发后续一系列操作了。
当调用Py_XDECREF
时,实践执行了如下的操作:
static inline void _Py_XDECREF(PyObject *op)
{
if (op != NULL) {
Py_DECREF(op);
}
}
#define Py_XDECREF(op) _Py_XDECREF(_PyObject_CAST(op))
define Py_DECREF(op) _Py_DECREF(__FILE__, __LINE__, _PyObject_CAST(op))
static inline void _Py_DECREF(const char *filename, int lineno, PyObject *op)
{
#ifdef Py_REF_DEBUG
_Py_RefTotal--;
#endif
if (--op->ob_refcnt != 0) {
#ifdef Py_REF_DEBUG
if (op->ob_refcnt < 0) {
_Py_NegativeRefcount(filename, lineno, op);
}
#endif
}
else {
_Py_Dealloc(op);
}
#endif
}
void
_Py_Dealloc(PyObject *op)
{
destructor dealloc = Py_TYPE(op)->tp_dealloc;
#ifdef Py_TRACE_REFS
_Py_ForgetReference(op);
#endif
(*dealloc)(op);
}
能够看到,假如目标内部的ob_refcnt
引证计数归0,就会触发_Py_Dealloc
逻辑,清空给目标分配的内存。
针对test_ref
的比如,会调用py3的字符串析构逻辑,也就是unicode
的dealloc
逻辑:
static void
unicode_dealloc(PyObject *unicode)
{
switch (PyUnicode_CHECK_INTERNED(unicode)) {
case SSTATE_NOT_INTERNED:
break;
case SSTATE_INTERNED_MORTAL:
{
struct _Py_unicode_state *state = get_unicode_state();
assert(Py_REFCNT(unicode) == 0);
Py_SET_REFCNT(unicode, 3);
if (PyDict_DelItem(state->interned, unicode) != 0) {
_PyErr_WriteUnraisableMsg("deletion of interned string failed",
NULL);
}
assert(Py_REFCNT(unicode) == 1);
Py_SET_REFCNT(unicode, 0);
break;
}
case SSTATE_INTERNED_IMMORTAL:
_PyObject_ASSERT_FAILED_MSG(unicode, "Immortal interned string died");
break;
default:
Py_UNREACHABLE();
}
if (_PyUnicode_HAS_WSTR_MEMORY(unicode)) {
PyObject_Free(_PyUnicode_WSTR(unicode));
}
if (_PyUnicode_HAS_UTF8_MEMORY(unicode)) {
PyObject_Free(_PyUnicode_UTF8(unicode));
}
if (!PyUnicode_IS_COMPACT(unicode) && _PyUnicode_DATA_ANY(unicode)) {
PyObject_Free(_PyUnicode_DATA_ANY(unicode));
}
Py_TYPE(unicode)->tp_free(unicode);
}
在unicode
的析构逻辑中,首要判别字符串是不是intern
的(短字符串缓存),假如是的话会启用别的的析构逻辑,否则会跑到下面。终究调用的是Py_TYPE(unicode)->tp_free(unicode)
逻辑。
void
PyObject_Free(void *ptr)
{
_PyObject.free(_PyObject.ctx, ptr);
}
// _PyObject.free(_PyObject.ctx, ptr)
// python_d.exe
static void
_PyMem_DebugFree(void *ctx, void *ptr)
{
_PyMem_DebugCheckGIL(__func__);
_PyMem_DebugRawFree(ctx, ptr);
}
static void
_PyMem_DebugRawFree(void *ctx, void *p)
{
/* PyMem_Free(NULL) has no effect */
if (p == NULL) {
return;
}
debug_alloc_api_t *api = (debug_alloc_api_t *)ctx;
uint8_t *q = (uint8_t *)p - 2*SST; /* address returned from malloc */
size_t nbytes;
_PyMem_DebugCheckAddress(__func__, api->api_id, p);
nbytes = read_size_t(q);
nbytes += PYMEM_DEBUG_EXTRA_BYTES;
memset(q, PYMEM_DEADBYTE, nbytes);
api->alloc.free(api->alloc.ctx, q);
}
// api->alloc.free(api->alloc.ctx, q)
static void
_PyObject_Free(void *ctx, void *p)
{
/* PyObject_Free(NULL) has no effect */
if (p == NULL) {
return;
}
if (UNLIKELY(!pymalloc_free(ctx, p))) {
/* pymalloc didn't allocate this address */
PyMem_RawFree(p);
raw_allocated_blocks--;
}
}
一路走下来,终究会调用_PyObject_Free
去完全开释这块内存,_PyObject_Free
会测验经过pymalloc_free
和PyMem_RawFree
两种办法对目标所占内存进行开释。其间前者是选用python
自带的内存办理机制,后者是选用操作系统的free
办法。
这里我们需要略微了解一下python
内存办理的机制。python
内部维护了不同组相同巨细块的内存池,其间有几个概念:
-
arena
:办理一组pool
- 维护可用
pool
数量及pool
总量 - 能够有多个
arena
- 维护可用
-
pool
:办理一组相同巨细的block
的链表- 正在运用中(
used
,非空,非满)的pool
集合,会独自由usedpools
数组办理 - 请求内存时,会优先从
usedpools
寻觅可用pool
。假如block
数量不够,会新增block
- 假如没有指定巨细的
block
,会从arena
新起一个pool
然后分配对应block
- 开释目标内存时,被开释的
block
被转移到独自的可用block
链表
- 正在运用中(
-
block
:一个固定巨细的内存块- 在
python
中,有不同的固定巨细,以8/16字节对齐(ALIGNMENT
宏) - 针对不同巨细的目标,分配不同巨细的
block
- 在
了解了这些概念,再看python
目标的内存回收逻辑,就很明白了:
static inline int
pymalloc_free(void *ctx, void *p)
{
assert(p != NULL);
poolp pool = POOL_ADDR(p);
if (UNLIKELY(!address_in_range(p, pool))) {
return 0;
}
assert(pool->ref.count > 0); /* else it was empty */
block *lastfree = pool->freeblock;
*(block **)p = lastfree;
pool->freeblock = (block *)p;
pool->ref.count--;
if (UNLIKELY(lastfree == NULL)) {
insert_to_usedpool(pool);
return 1;
}
if (LIKELY(pool->ref.count != 0)) {
/* pool isn't empty: leave it in usedpools */
return 1;
}
insert_to_freepool(pool);
return 1;
}
终究我们能够看到,pymalloc_free
首要做了以下几件事情,完成目标的内存开释:
- 经过
POOL_ADDR
宏,找到指针p
对应pool
方位 - 将指针
p
对应的内存块放到freeblock
链表头部 - 削减
pool
的引证数。假如归零,将pool
放到对应arena
的freepools
里