You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何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 =128
  • 128 & ~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=1330
  • 10641+1330+6=11977
  • 11977&~3=11976(11977是奇数,减1得到11976,4的倍数)

结果同样完全匹配。

总结

你之前的计算错误是把new_allocated(元素个数)直接当成了字节数,忽略了指针大小和列表结构体的开销;同时测试用例使用了列表推导式,没有触发分步扩容逻辑,导致误解。改用逐步append的测试方式,并将new_allocated转换为字节数后,结果和源码公式完全一致。

内容的提问来源于stack exchange,提问作者Andrew H

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 17:28:14