[1,2^30)
提供的28
范围内的Python整数对象需要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
倍,加上很小的开销。答案 0 :(得分:2)
int
对象仅需要28个字节,但是Python使用8个字节的对齐方式:内存以8字节大小倍数的块分配。因此,每个int
对象使用的实际内存为32个字节。有关更多详细信息,请参见Python memory management上的这篇出色文章。
我还没有剩下的半个字节的解释,但是如果发现一个半字节,我会进行更新。
答案 1 :(得分:1)
longint
实现的一些细节。根据他的解释,
...
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
,即
1000+i
的32个字节(1<<30)+i
的36个字节(1<<60)+i
的40个字节之所以会发生这种情况,是因为在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
分配器使用内存块对齐8
个字节,这意味着36
个字节将存储在大小为40
的块中-与(1<<60)+i
的结果相同(保留额外的8个字节记住指针)。
要解释剩余的0.5
字节,我们需要更深入地研究py_malloc
-分配器的细节。 source-code itself是一个很好的概述,我最后一次描述它的方法可以在此SO-post中找到。
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
字节块,这意味着一个池具有{ {3}}:
/* 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
的{{1}}字节占用空间,另外还有8个字节的指针。1000+i
需要(30<<1)+i
个字节,这意味着40
可以容纳4Kb
个块,其中一个(划分池时剩余102
个字节在16
个字节块中,它们可以用于40
,用于pool_header
,这导致pool_header
个字节(加上4Kb/101=40.55
个字节指针)。我们还可以看到,还有一些额外的开销负责每个整数8
个字节-不够我照顾。