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

范围内的 Python-Integer-objects[1,2^30)需要28字节,如sys.getsizeof()在此 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字节大。

  • list 中没有过度分配lst,可以很容易地检查sys.getsizeof(lst)它,它产生8了元素数量的倍数,加上非常小的开销。


慕田峪7331174
浏览 268回答 2
2回答

暮色呼如

longint@Nathan的建议令人惊讶地不是解决方案,因为 CPython 的实现有一些微妙的细节。根据他的解释,内存占用为...lst[i] = (1<<30)+i应该仍然是40.52,因为sys.sizeof(1<<30)是32,但测量结果表明它是48.56。另一方面,对于...lst[i] = (1<<60)+i48.56尽管事实上,足迹仍然sys.sizeof(1<<60)是36。原因是:sys.getsizeof()不告诉我们真实的内存占用为求和的结果,即a+b是32 字节 1000+i36 字节用于 (1<<30)+i40 字节用于 (1<<60)+i发生这种情况,因为当两个整数的相加x_add,所得到的整数,在具有第一个“数字”,即4个字节,超过最大的a和b:static PyLongObject *x_add(PyLongObject *a, PyLongObject *b){&nbsp; &nbsp; Py_ssize_t size_a = Py_ABS(Py_SIZE(a)), size_b = Py_ABS(Py_SIZE(b));&nbsp; &nbsp; PyLongObject *z;&nbsp; &nbsp; ...&nbsp; &nbsp; /* Ensure a is the larger of the two: */&nbsp; &nbsp; ...&nbsp; &nbsp; z = _PyLong_New(size_a+1);&nbsp;&nbsp;&nbsp; &nbsp; ...加法后结果被归一化:&nbsp;...&nbsp;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 {&nbsp; &nbsp; union { block *_padding;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; uint count; } ref;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; /* number of allocated blocks&nbsp; &nbsp; */&nbsp; &nbsp; block *freeblock;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;/* pool's free list head&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;*/&nbsp; &nbsp; struct pool_header *nextpool;&nbsp; &nbsp; &nbsp; &nbsp;/* next pool of this size class&nbsp; */&nbsp; &nbsp; struct pool_header *prevpool;&nbsp; &nbsp; &nbsp; &nbsp;/* previous pool&nbsp; &nbsp; &nbsp; &nbsp;""&nbsp; &nbsp; &nbsp; &nbsp; */&nbsp; &nbsp; uint arenaindex;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; /* index into arenas of base adr */&nbsp; &nbsp; uint szidx;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;/* block size class index&nbsp; &nbsp; &nbsp; &nbsp; */&nbsp; &nbsp; uint nextoffset;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; /* bytes to virgin block&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;*/&nbsp; &nbsp; uint maxnextoffset;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;/* largest valid nextoffset&nbsp; &nbsp; &nbsp; */};这个结构的大小在我的 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每个整数字节 - 不足以让我关心。

智慧大石

该int对象只需要 28 个字节,但 Python 使用 8 字节对齐:内存分配在大小为 8 个字节的倍数的块中。所以每个int对象实际使用的内存是 32 字节。有关更多详细信息,请参阅这篇关于Python 内存管理的优秀文章。我还没有对剩余的半字节做出解释,但如果我找到了,我会更新它。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Python