python的内存分配
python的内存分配主要分为4层。在Objects/obmalloc.c中的注释里画出了python的内存分配模型如下。
截图来自python代码的注释
其中第0层是库函数的内存分配器,例如malloc。第一层是python对于malloc,calloc,realloc,free所做的封装。第二层是python的object的分配。第三层是具体对象的分配,例如dict等。
第0层
其中最底层的0层是基础的分配器,可以理解为malloc这样的库函数。只有当申请的内存大小大于512字节的时候才会调用malloc。这个阈值在宏SMALL_REQUEST_THRESHOLD中定义,在早先版本的python中是256字节。
例如在Objects/obmalloc.c的_PyObject_Malloc中:
static void *
_PyObject_Malloc(void *ctx, size_t nbytes)
{
void* ptr;
if (pymalloc_alloc(ctx, &ptr, nbytes)) {
return ptr;
}
ptr = PyMem_RawMalloc(nbytes);
if (ptr != NULL) {
raw_allocated_blocks++;
}
return ptr;
}
其中pymalloc_alloc会校验被申请的内存空间大小,如果大于512字节会返回空。而PyMem_RawMalloc就是malloc的一层封装。
第1层
python内存分配器的第一层就是对于malloc,alloc,realloc,free这四个库函数做了一层封装。在python的代码中,对应这四个库函数分别有PyMem_RawMalloc, PyMem_RawCalloc, PyMem_RawRealloc和PyMem_RawFree。以下是PyMem_RawMalloc的实现:
void *
PyMem_RawMalloc(size_t size)
{
if (size > (size_t)PY_SSIZE_T_MAX)
return NULL;
return _PyMem_Raw.malloc(_PyMem_Raw.ctx, size);
}
抛开异常处理之外,可以看到主要就是调用了绑定在_PyMem_Raw之上的一个malloc成员函数。先说结论:这个malloc函数根据宏定义的不同会绑定到_PyObject_Malloc或者是_PyMem_RawMalloc。以下是定义_PyMem_Raw的相关代码:
typedef struct {
/* user context passed as the first argument to the 4 functions */
void *ctx;
/* allocate a memory block */
void* (*malloc) (void *ctx, size_t size);
/* allocate a memory block initialized by zeros */
void* (*calloc) (void *ctx, size_t nelem, size_t elsize);
/* allocate or resize a memory block */
void* (*realloc) (void *ctx, void *ptr, size_t new_size);
/* release a memory block */
void (*free) (void *ctx, void *ptr);
} PyMemAllocatorEx;
#define MALLOC_ALLOC {NULL, _PyMem_RawMalloc, _PyMem_RawCalloc, _PyMem_RawRealloc, _PyMem_RawFree}
#ifdef WITH_PYMALLOC
# define PYMALLOC_ALLOC {NULL, _PyObject_Malloc, _PyObject_Calloc, _PyObject_Realloc, _PyObject_Free}
#endif
#define PYRAW_ALLOC MALLOC_ALLOC
static PyMemAllocatorEx _PyMem_Raw = PYRAW_ALLOC;
所以可以看出,只要声明了WITH_PYMALLOC这个宏,那么对应的四个操作就会使用如下的对应,这四个函数都会在对于分配内存空间不超过512字节的情况下使用python自己的内存池:
_PyObject
malloc
_PyObject_Malloc
calloc
_PyObject_Calloc
realloc
_PyObject_Realloc
free
_PyObject_Free
否则使用的是如下对应,这四个函数只是对于库函数做了一层封装。
_PyMem_Raw
malloc
_PyMem_RawMalloc
calloc
_PyMem_RawCalloc
realloc
_PyMem_RawRealloc
free
_PyMem_RawFree
它们之间的区别是是否使用python的内存管理机制(即只有超过512字节才直接使用malloc等库函数,否则使用内存池分配)。
只有在python2.3的版本之前需要自己手动打开WITH_PYMALLOC这个宏,现在的版本默认使用的是python的内存管理机制。
python内存池
当申请的对象不超过512字节时,对象从python内存池分配空间。
python的内存池同时和第一层以及第二层有关系。具体的层次关系如下图:
内存池的层次关系
包含关系很明显,arena包含pool,pool包含block。
只有arena是通过malloc从系统中申请到的内存,它的大小固定都是256k。每次内存池空间不足的时候都通过new_arena()申请新的arena。arena通过Objects/obmalloc.c里的arenas数组管理,如图所示:
arenas的结构
另外全局变量unused_arena_objects用于将现在未使用的arena串联进这个单链表。无可用arena的时候,优先从这个链表里寻找内存空间。
对于arena包含的pool,这里需要说明的是,pool并不由arena管理。例如我们需要申请一块内存block的时候,并不是先找到一个arena,然后从它拥有的pool分配一个block。
每个arena中的pool大小都是固定4k字节(一个内存页的大小)。每个pool中所包含的block大小也是固定的。具体的对应关系如下图:
申请内存空间大小和block大小的对应
所以如果申请长度为7的空间,就会返回一个大小为8的block。申请大小为68的空间,就会返回一个大小为72的block。
pool在内存管理中由usedpool来组织,它们的关系如下图:
Popo截图20191125154543.png
数组中的每个元素都是一个链表头,链表串起了所有有空闲,且block大小相等的pool。很显然,当申请长度为n的空间时,对应的链表一定在usedpools的(n+1)/8的位置。
这里需要注意的是,链表中串联的是待分配的pool。如果一个pool里的所有block都被释放了,它会被从这个链表里移除。这么做的原因应该是尽量保证先使用已经被占用了一部分的内存空间。
对于block就比较简单,只有被占用,被释放和未初始化三个状态。block被释放了之后连接到pool的freeblock链表上。
回顾一下一开始_PyObject_Malloc的代码:
static void *
_PyObject_Malloc(void *ctx, size_t nbytes)
{
void* ptr;
// 从内存池中获取一块内存
if (pymalloc_alloc(ctx, &ptr, nbytes)) {
return ptr;
}
ptr = PyMem_RawMalloc(nbytes);
if (ptr != NULL) {
raw_allocated_blocks++;
}
return ptr;
}
这里的pymalloc_alloc所做的就是第二层的工作,最终返回一个block的地址。
第3层
这一层就是对象自己的分配器。以字典对象的分配PyDict_New为例:
// Objects/dictobject.c
PyObject * PyDict_New(void)
{
// 增加空字典引用 和本文无关
dictkeys_incref(Py_EMPTY_KEYS);
return new_dict(Py_EMPTY_KEYS, empty_values);
}
/* Consumes a reference to the keys object */
static PyObject *
new_dict(PyDictKeysObject *keys, PyObject **values)
{
PyDictObject *mp;
assert(keys != NULL);
if (numfree) {
// free_list是空闲对象的数组,有对象被释放之后优先从空闲对象里分配
mp = free_list[--numfree];
assert (mp != NULL);
assert (Py_TYPE(mp) == &PyDict_Type);
_Py_NewReference((PyObject *)mp);
}
else {
// 分配
mp = PyObject_GC_New(PyDictObject, &PyDict_Type);
if (mp == NULL) {
dictkeys_decref(keys);
if (values != empty_values) {
free_values(values);
}
return NULL;
}
}
mp->ma_keys = keys;
mp->ma_values = values;
mp->ma_used = 0;
mp->ma_version_tag = DICT_NEXT_VERSION();
ASSERT_CONSISTENT(mp);
return (PyObject *)mp;
}
这里调用PyObject_GC_New,最终对应到的是Modules/gcmodule.c里的_PyObject_GC_Alloc。以下是_PyObject_GC_Alloc的代码:
static PyObject * _PyObject_GC_Alloc(int use_calloc, size_t basicsize)
{
struct _gc_runtime_state *state = &_PyRuntime.gc;
PyObject *op;
PyGC_Head *g;
size_t size;
if (basicsize > PY_SSIZE_T_MAX - sizeof(PyGC_Head))
return PyErr_NoMemory();
size = sizeof(PyGC_Head) + basicsize;
// 这里是最终获取内存空间的调用
if (use_calloc)
g = (PyGC_Head *)PyObject_Calloc(1, size);
else
g = (PyGC_Head *)PyObject_Malloc(size);
if (g == NULL)
return PyErr_NoMemory();
assert(((uintptr_t)g & 3) == 0); // g must be aligned 4bytes boundary
g->_gc_next = 0;
g->_gc_prev = 0;
// 这部分是将新生成的obj连入gc链表,与本文无关
state->generations[0].count++; /* number of allocated GC objects */
if (state->generations[0].count > state->generations[0].threshold &&
state->enabled &&
state->generations[0].threshold &&
!state->collecting &&
!PyErr_Occurred()) {
state->collecting = 1;
collect_generations(state);
state->collecting = 0;
}
op = FROM_GC(g);
return op;
}
可以看出,排除了将生成的object连入gc链表的操作,本质上就是调用PyObject_Malloc或者PyObject_Calloc获取内存空间。