gpt4 book ai didi

python - 与 sys.getsizeof() 的结果相比,整数的大内存占用

转载 作者:太空狗 更新时间:2023-10-30 02:51:31 27 4
gpt4 key购买 nike

[1,2^30) 范围内的 Python 整数对象需要 28 字节,由 sys.getsizeof() 并在 this SO-post 中进行了举例说明.

但是,当我使用以下脚本测量内存占用量时:

#int_list.py:
import sys

N=int(sys.argv[1])
lst=[0]*N # no overallocation

for i in range(N):
lst[i]=1000+i # ints not from integer pool

通过

/usr/bin/time -fpeak_used_memory:%M python3 int_list.py <N>

我得到以下峰值内存值(Linux-x64、Python 3.6.2):

   N     Peak memory in Kb        bytes/integer
-------------------------------------------
1 9220
1e7 404712 40.50
2e7 800612 40.52
3e7 1196204 40.52
4e7 1591948 40.52

所以看起来好像每个整数对象需要 40.5 字节,即 12.5 字节比 sys.getsizeof() 产生的多.

额外的 8 字节很容易解释 - 列表 lst 不包含整数对象,而是对它们的引用 - 这意味着一个额外的指针,即 8 字节,是必需的。

但是,另外的4.5字节呢,它们有什么用呢?

可以排除以下原因:

  • 整数对象的大小是可变的,但是 10^7 小于 2^30 因此所有整数都是 28 字节大。
  • 列表 lst 中没有过度分配,可以通过 sys.getsizeof(lst) 轻松检查,它产生 8 倍元素数量,加上非常小的开销。

最佳答案

由于 CPython 的 longint 的一些微妙细节,@Nathan 的建议令人惊讶地不是解决方案。 -执行。根据他的解释,

...
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 个字节不值得,可以找到函数的源代码 here .


现在,我们可以使用@Nathans 的洞察力来解释,为什么 (1<<30)+i 的足迹是48.56而不是 44.xy : 二手py_malloc -allocator 使用对齐方式为 8 的内存块字节,这意味着 36字节将存储在大小为 40 的 block 中- 与 (1<<60)+i 的结果相同(记住额外的 8 字节用于指针)。


解释剩下的0.5 bytes 我们需要深入了解 py_malloc 的细节-分配器。一个很好的概述是 source-code itself ,我最后一次尝试描述它可以在这个 SO-post 中找到.

简而言之,分配器管理arenas中的内存,每个256MB。分配竞技场时,会保留内存,但不会提交。我们将内存视为“已使用”,仅当所谓的 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 */
};

这个结构的大小是48 (称为 POOL_OVERHEAD )字节在我的 Linux_64 机器上。这pool_header是池的一部分(通过 cruntime-memory-allocator 避免额外分配的一种非常聪明的方法)并将取代两个 32 -byte-blocks,这意味着一个池有 place for 126 32 byte integers :

/* 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字节,这意味着 4Kb102 的位置 block ,其中一个(当pool分成16字节 block 时还有剩余40字节,可用于pool_header)用于pool_header ,这导致 4Kb/101=40.55字节(加上 8 字节指针)。

我们还可以看到,有一些额外的开销,负责 ca。 0.01每个整数的字节 - 不够大,我不关心。

关于python - 与 sys.getsizeof() 的结果相比,整数的大内存占用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55595549/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com