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 內存管理的優秀文章。
我還沒有對剩余的半字節做出解釋,但如果我找到了,我會更新它。
添加回答
舉報