为何Python列表实际内存占用与文档计算逻辑不符?
问题分析与解答
你的问题源于两个关键误解:测试用例的逻辑偏差和对源码变量含义的混淆,具体解释如下:
1. 测试用例的问题:列表推导式不触发分步扩容
你使用[i for i in range(n)]创建列表时,CPython会预先计算元素总数,一次性分配足够容纳所有元素的内存(或少量冗余),不会模拟append操作的分步扩容流程。因此这种方式得到的内存大小,和append触发的扩容公式没有直接关系。
正确的测试方式应该是通过循环逐步append元素,模拟真实的扩容场景:
import sys # 模拟逐步append到108个元素 a = [] for i in range(108): a.append(i) print(sys.getsizeof(a)) # 模拟逐步append到109个元素 b = [] for i in range(109): b.append(i) print(sys.getsizeof(b))
2. 混淆了new_allocated的含义:元素个数≠字节数
源码中的new_allocated表示列表缓冲区可容纳的元素个数,不是字节数。要得到sys.getsizeof返回的总内存大小,需要结合两个因素:
- 每个元素在列表中是一个指针(64位系统为8字节,32位为4字节)
- 列表结构体本身的内存开销(取决于Python版本和编译选项,比如开启
Py_TRACE_REFS时,结构体大小为56字节;默认发行版为40字节)
以你的测试结果为例:
- 对于109个元素的列表
b,sys.getsizeof(b)=1080字节 - 假设你的Python版本开启了
Py_TRACE_REFS,结构体大小为56字节 - 缓冲区字节数 = 1080 - 56 = 1024字节
- 缓冲区可容纳元素数 = 1024 / 8 = 128个
而按源码公式计算:
new_allocated = ((size_t)109 + (109 >> 3) + 6) & ~(size_t)3;
计算过程:
109 >> 3= 13(等价于109//8)109 +13 +6 =128128 & ~3 =128(保证是4的倍数)
结果完全匹配:new_allocated=128个元素,对应缓冲区字节数128*8=1024,加上结构体56字节,总大小1080字节。
再看大列表的例子:
b有10641个元素,sys.getsizeof(b)=95864字节- 缓冲区字节数=95864-56=95808字节
- 元素个数=95808/8=11976个
按公式计算:
new_allocated = ((size_t)10641 + (10641 >>3)+6) & ~3;
10641>>3=133010641+1330+6=1197711977&~3=11976(11977是奇数,减1得到11976,4的倍数)
结果同样完全匹配。
总结
你之前的计算错误是把new_allocated(元素个数)直接当成了字节数,忽略了指针大小和列表结构体的开销;同时测试用例使用了列表推导式,没有触发分步扩容逻辑,导致误解。改用逐步append的测试方式,并将new_allocated转换为字节数后,结果和源码公式完全一致。
内容的提问来源于stack exchange,提问作者Andrew H
相关产品推荐
相关产品推荐

