python - CPython的静态对象地址和碎片

标签 python memory-management cpython

我读过

For CPython, id(x) is the memory address where x is stored.

给定的对象的id永远不会改变,这意味着对象在其生命周期中始终存储在给定的内存地址中。这就引出了一个问题:(虚拟)内存碎片怎么样?

假设一个对象A位于地址1(具有id 1),占用10个字节,因此它占用地址1-10。对象Bid为11,占用字节11-12,对象C占用地址13-22。一旦 B 超出范围并被 GC 回收,我们就会产生碎片。

这个难题如何解决?

最佳答案

CPython 对小对象使用自己的内存分配器,pymalloc-allocator 。一个相当好的描述可以在 code itself 中找到。 。

这个分配器非常擅长避免内存碎片,因为它有效地重用了释放的内存。然而,这只是一种启发式方法,人们可能会想出导致内存碎片的场景。

让我们看看当我们分配一个大小为 1 字节的对象时会发生什么。

CPython 对于小于 512 字节的对象有它自己的所谓的 arena。显然,1 字节请求将由其分配器管理。

请求的大小分为 64 个不同的类别:第 0 类适用于 1..8 字节的大小,第 1 类适用于 9..16 字节的大小,依此类推 - 这是由于需要 8 字节对齐。上述每个类都有自己或多或少的独立/专用内存。我们的要求是 0 级。

假设这是对此尺寸级别的第一个请求。将创建一个新的“池”或重新使用一个空池。游泳池是 4KB big ,因此有 512 个 8 字节“ block ”的空间。尽管只请求 1 个字节,但我们将阻塞占用 block 的另外 7 个字节,因此它们不能用于其他对象。所有空闲 block 都保存在一个列表中 - 一开始所有 512 个 block 都在此列表中。分配器从该空闲 block 列表中删除第一个 block ,并将其地址作为指针返回。

池本身被标记为“已使用”,并添加到 0 级的已用池列表中。

现在,分配另一个大小 <=8 字节的对象的情况如下。首先,我们查看第 0 类的已用池列表,并会找到一个已在使用的池,即有一些已使用的 block 和一些空闲 block 。分配器使用第一个空闲 block ,将其从空闲 block 列表中删除,并将其地址作为指针返回。

删除第一个对象很容易 - 我们将占用的 block 添加为(到目前为止单个)已使用池中的空闲 block 列表的头部。

当创建 8 字节的新对象时,将使用空闲 block 列表中的第一个 block ,这是第一个现已删除的对象使用的 block 。

正如您所看到的,内存被重用,因此内存碎片大大减少。这并不意味着不能存在内存碎片:

分配 512 个 1 字节对象后,第一个池将变“满”,并且将创建/使用第 0 类大小的新池。一旦我们添加了另外 512 个对象,第二个池也将变得“满”。等等。

现在,如果删除前 511 个元素,仍然会有一个字节阻塞整个 4KB,不能用于其他类。

只有当最后一个 block 被释放时,池才变为“空”,因此可以重新用于其他大小类别。


空池不会返回给操作系统,而是留在竞技场中并被重用。然而,pymalloc manages multiple arenas ,如果竞技场变得“未使用”,它可能会被释放,并将占用的内存(即池)返回给操作系统。

关于python - CPython的静态对象地址和碎片,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54998210/

相关文章:

iphone - 如何处理iPhone内存不足的警告?

linux - 对线程使用 ps

python - OrderedDict 推导式

python - CPython - 在内部,堆栈和堆中存储了什么?

cpython - PyPy cpyext : any documentation? 怎么用? PyThreadState_Get 错误?

python - 动态规划: recursion+memoization vs loop+list

python - 服务器如何区分使用 TBB 的用户和仅使用 Tor 作为代理的用户?

python - 如何在Python中写入多个csv文件

python - 如何在应用程序退出时正确终止线程

iphone - 跟进 viewDidUnload vs. dealloc 问题