2 回答

TA贡献1853条经验 获得超9个赞
longint@Nathan的建议令人惊讶地不是解决方案,因为 CPython 的实现有一些微妙的细节。根据他的解释,内存占用为
...
lst[i] = (1<<30)+i
应该仍然是40.52,因为sys.sizeof(1<<30)是32,但测量结果表明它是48.56。另一方面,对于
...
lst[i] = (1<<60)+i
48.56尽管事实上,足迹仍然sys.sizeof(1<<60)是36。
原因是:sys.getsizeof()不告诉我们真实的内存占用为求和的结果,即a+b是
32 字节 1000+i
36 字节用于 (1<<30)+i
40 字节用于 (1<<60)+i
发生这种情况,因为当两个整数的相加x_add,所得到的整数,在具有第一个“数字”,即4个字节,超过最大的a和b:
static PyLongObject *
x_add(PyLongObject *a, PyLongObject *b)
{
Py_ssize_t size_a = Py_ABS(Py_SIZE(a)), size_b = Py_ABS(Py_SIZE(b));
PyLongObject *z;
...
/* Ensure a is the larger of the two: */
...
z = _PyLong_New(size_a+1);
...
加法后结果被归一化:
...
return long_normalize(z);
};
即可能的前导零被丢弃,但内存没有释放 - 4 个字节不值得,函数的来源可以在这里找到。
现在,我们可以使用@Nathans 的洞察力来解释为什么(1<<30)+iis48.56和 not的足迹44.xy:使用的py_malloc-allocator 使用内存块对齐8字节,这意味着36字节将存储在一个大小的块中40- 与结果相同(1<<60)+i(记住指针的额外 8 字节)。
为了解释剩余的0.5字节,我们需要更深入地了解py_malloc-allocator 的细节。一个很好的概述是源代码本身,我最后一次尝试描述它可以在这个SO-post 中找到。
简而言之,分配器管理 arenas 中的内存,每个内存为 256MB。分配 arena 时,会保留内存,但不会提交。我们将内存视为“已使用”,只有当一个所谓的pool被触及时。池4Kb很大 ( POOL_SIZE) 并且仅用于具有相同大小的内存块 - 在我们的例子中是32字节。这意味着 的分辨率peak_used_memory为 4Kb,不能对这些0.5字节负责。
但是,必须管理这些池,这会导致额外的开销:每个池都py_malloc需要一个pool_header:
/* Pool for small blocks. */
struct pool_header {
union { block *_padding;
uint count; } ref; /* number of allocated blocks */
block *freeblock; /* pool's free list head */
struct pool_header *nextpool; /* next pool of this size class */
struct pool_header *prevpool; /* previous pool "" */
uint arenaindex; /* index into arenas of base adr */
uint szidx; /* block size class index */
uint nextoffset; /* bytes to virgin block */
uint maxnextoffset; /* largest valid nextoffset */
};
这个结构的大小在我的 Linux_64 机器上是48(称为POOL_OVERHEAD)字节。这pool_header是池的一部分(避免通过 cruntime-memory-allocator 进行额外分配的一种非常聪明的方法),并将取代两个32字节块,这意味着池有用于126 32字节整数的位置:
/* Return total number of blocks in pool of size index I, as a uint. */
#define NUMBLOCKS(I) ((uint)(POOL_SIZE - POOL_OVERHEAD) / INDEX2SIZE(I))
这导致:
4Kb/126 = 32.51的字节足迹1000+i,加上额外的 8 个字节的指针。
(30<<1)+i需要40字节,这意味着4Kb有102块的地方,其中一个(16当池划分为40-bytes块时还有剩余的字节,它们可以用于pool_header)用于pool_header,这导致4Kb/101=40.55字节(加上8字节指针)。
我们还可以看到,还有一些额外的开销,负责 ca。0.01每个整数字节 - 不足以让我关心。

TA贡献1946条经验 获得超3个赞
该int
对象只需要 28 个字节,但 Python 使用 8 字节对齐:内存分配在大小为 8 个字节的倍数的块中。所以每个int
对象实际使用的内存是 32 字节。有关更多详细信息,请参阅这篇关于Python 内存管理的优秀文章。
我还没有对剩余的半字节做出解释,但如果我找到了,我会更新它。
添加回答
举报