内存模型
问题
Python 对象在内存中是如何布局的?小整数池和字符串驻留是怎么回事?Python 的内存分配器是怎样的?
答案
对象内存布局
每个 Python 对象至少包含:
┌────────────────────┐
│ ob_refcnt (8B) │ 引用计数
├────────────────────┤
│ ob_type (8B) │ 类型指针 → type 对象
├────────────────────┤
│ 对象数据... │ 具体类型的数据
└────────────────────┘
import sys
# 空对象的基本大小
print(sys.getsizeof(object())) # 16 bytes(refcnt + type)
print(sys.getsizeof(1)) # 28 bytes
print(sys.getsizeof("")) # 49 bytes
print(sys.getsizeof([])) # 56 bytes
print(sys.getsizeof({})) # 64 bytes
内存分配器层次
- Block:固定大小的内存块(8 的倍数,8~512 bytes)
- Pool:4KB,包含相同大小的 Block
- Arena:256KB,包含多个 Pool
小对象(≤ 512 bytes)由 pymalloc 管理,大对象直接用系统 malloc。
小整数池与字符串驻留
# 小整数池:CPython 缓存 [-5, 256]
a = 256
b = 256
print(a is b) # True
a = 257
b = 257
print(a is b) # False(交互模式下;脚本中可能被编译器优化为 True)
# 字符串驻留:标识符类字符串自动驻留
a = "hello"
b = "hello"
print(a is b) # True
a = "hello world" # 含空格
b = "hello world"
print(a is b) # False(不自动驻留)
# 手动驻留
import sys
a = sys.intern("hello world")
b = sys.intern("hello world")
print(a is b) # True
常见面试问题
Q1: 为什么 Python 的 int 比 C 的 int 大这么多?
答案:
Python 的 int 是对象,包含引用计数(8B)、类型指针(8B)、值长度(8B)和实际数值(至少 4B),所以最小 28 bytes。而 C 的 int 只有 4 bytes。这是动态类型语言的代价。
Q2: 如何测量 Python 对象的内存占用?
答案:
import sys
# 浅层大小(不含引用的对象)
sys.getsizeof([1, 2, 3]) # 88 bytes(列表本身)
# 深层大小需要递归计算
def deep_getsizeof(obj, seen=None):
if seen is None:
seen = set()
obj_id = id(obj)
if obj_id in seen:
return 0
seen.add(obj_id)
size = sys.getsizeof(obj)
if isinstance(obj, dict):
size += sum(deep_getsizeof(k, seen) + deep_getsizeof(v, seen) for k, v in obj.items())
elif isinstance(obj, (list, tuple, set)):
size += sum(deep_getsizeof(i, seen) for i in obj)
return size