CPython的静态对象地址和碎片

问题描述 投票:2回答:1

我读

对于Python,if(x)是存储x的内存地址。

并且它是一个给定的对象的id永远不会改变,这意味着一个对象总是在其生命周期中存储在给定的内存地址。这导致了一个问题:(虚拟)内存碎片怎么样?

假设一个对象A位于地址1(有id 1),占用10个字节,所以占用地址1-10。对象Bid 11并占用字节11-12,对象C占用地址13-22。一旦B超出范围并获得GC,我们就会出现碎片。

这个难题是如何解决的?

python memory-management cpython
1个回答
3
投票

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

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

让我们来看看当我们分配大小为1byte的对象时会发生什么。

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

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

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

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

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

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

当创建一个8字节的新对象时,使用free-block-list中的第一个块,这是第一个现在删除的对象使用的块。

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

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

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

仅当释放最后一个块时,池才变为“空”,因此可以重用于其他大小类。


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

© www.soinside.com 2019 - 2024. All rights reserved.