大家好,我是正在实战各种 AI 项目的程序员晚枫。

为什么 Python 小对象创建这么快?频繁创建销毁对象会不会导致内存碎片?

答案就在pymalloc 内存池。理解这个机制,你就能解释很多 Python 的内存行为,也能写出更省内存的代码。

想象一下,你去银行取钱。如果每次取 100 元,银行都从金库里现找,那效率太低了。所以银行会在柜台准备一些现金,小额取款直接从柜台拿,大额才去金库。Python 的内存管理也是类似的思路。


🏊 Python 内存分配架构

CPython 采用分层内存管理策略,不同大小的对象使用不同的分配方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌─────────────────────────────────────────┐
│ 用户代码层 │
│ obj = MyClass() → 请求内存 │
└──────────────────┬──────────────────────┘

┌─────────────────────────────────────────┐
│ 对象分配器(Object Allocator) │
│ ├── 小对象 (≤512 字节): pymalloc │
│ └── 大对象 (>512 字节): 直接使用 malloc │
└──────────────────┬──────────────────────┘

┌─────────────────────────────────────────┐
│ C 运行时层 │
│ malloc / free │
└──────────────────┬──────────────────────┘

┌─────────────────────────────────────────┐
│ 操作系统层 │
│ brk / mmap │
└─────────────────────────────────────────┘

关键阈值

对象大小分配方式特点
≤ 512 bytespymalloc快速,无锁(有 GIL),低碎片
> 512 bytesmalloc标准 C 分配,可能产生碎片

为什么是 512 字节这个阈值?这是经验值。统计表明,Python 程序中绝大多数临时对象都很小(整数、短字符串、小列表等),所以把阈值设得较低可以覆盖大部分场景。


🔧 pymalloc 内存池详解

核心概念

pymalloc 采用了三级结构来管理内存,这是理解它的关键:

1
Arena(256KB)→ Pool(4KB)→ Block(8~512 字节)

你可以把它想象成俄罗斯套娃:最大的套娃是 Arena,里面装着多个 Pool,每个 Pool 里面装着多个 Block。

1. Arena(竞技场)

Arena 是最大的内存单元,大小为 256KB。当 pymalloc 需要更多内存时,它会向操作系统申请 Arena。

1
2
3
4
5
6
7
8
9
10
11
12
// 一个 Arena 大小为 256KB
#define ARENA_SIZE (256 << 10) // 256 KB

struct arena_object {
uptr address; // Arena 的起始地址
block* pool_address; // 下一个可分配的 Pool 位置
uint nfreepools; // 空闲 Pool 数量
uint ntotalpools; // 总 Pool 数量
struct pool_header* freepools; // 空闲 Pool 链表
struct arena_object* nextarena;
struct arena_object* prevarena;
};

一个 Arena 可以容纳多少个 Pool?256KB ÷ 4KB = 64 个 Pool。

2. Pool(内存池)

Pool 是中间层,大小为 4KB(正好是系统页大小)。每个 Pool 专门服务于特定大小的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 一个 Pool 大小为 4KB(系统页大小)
#define POOL_SIZE SYSTEM_PAGE_SIZE // 4KB

struct pool_header {
union { block *_padding; uint count; } ref; // 已分配 block 数
block *freeblock; // 空闲 block 链表头
struct pool_header *nextpool; // 同 size class 的下一个 pool
struct pool_header *prevpool; // 同 size class 的上一个 pool
uint arenaindex; // 所属 arena 索引
uint szidx; // size class 索引
uint nextoffset; // 下一个可分配位置
uint maxnextoffset; // 最大可分配位置
};

Pool 的设计很巧妙:同一 Pool 内的所有 Block 大小相同。这样在分配时,不需要计算,直接链表摘取即可。

3. Size Class(大小分级)

这是 pymalloc 最精妙的设计。小对象按 8 字节对齐分为不同 size class:

1
8, 16, 24, 32, 40, ..., 512 字节

总共 64 个 size class。每个 size class 有自己独立的 Pool 链表。

1
2
3
4
5
6
7
8
9
// 计算 size class 索引
#define ALIGNMENT 8
#define INDEX2SIZE(I) (((uint)(I) + 1) << ALIGNMENT_SHIFT)

// 例如:
// index=0 → size=8
// index=1 → size=16
// index=2 → size=24
// ...

分配流程

当 Python 需要分配内存时,pymalloc 按以下流程处理:

1
2
3
4
5
6
7
8
9
10
11
请求内存 (nbytes)

计算 size class 索引

查找该 size class 的空闲 Pool

有可用 Pool?
├─ 是 → 从 Pool 中分配 Block
└─ 否 → 新建或复用 Pool

从 Arena 分配

这个过程非常快,因为大多数情况下只需要从链表中摘取一个 Block。

释放流程

释放内存同样高效:

1
2
3
4
5
6
7
8
9
释放指针 (p)

找到包含该指针的 Pool

将 Block 加入空闲链表

Pool 变空?
├─ 是 → 归还给 Arena
└─ 否 → 留在原地等待下次使用

📊 内存池的优势

性能对比

pymalloc 相比直接使用 malloc,性能提升显著:

场景mallocpymalloc提升倍数
小对象分配3-5 倍
小对象释放3-5 倍
内存碎片显著改善

为什么这么快?

  1. 无需系统调用:大多数分配直接从 Pool 链表摘取,不需要调用 malloc
  2. 无锁设计:有 GIL 保护,不需要额外的锁机制
  3. 大小分级:同一大小的对象复用 Pool,减少碎片
  4. 批量管理:Arena 级别批量申请和释放

内存碎片控制

策略效果
固定 size class减少外部碎片
Pool 复用减少内存申请次数
Arena 管理批量释放空闲内存

💡 实战:观察内存分配

使用 tracemalloc

Python 标准库提供了 tracemalloc 模块,可以跟踪内存分配:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import tracemalloc

# 开始跟踪
tracemalloc.start()

# 你的代码
snapshot1 = tracemalloc.take_snapshot()

# 创建大量对象
objects = [[i] for i in range(10000)]

snapshot2 = tracemalloc.take_snapshot()

# 比较差异
top_stats = snapshot2.compare_to(snapshot1, 'lineno')
for stat in top_stats[:5]:
print(stat)

# 查看当前内存使用
current, peak = tracemalloc.get_traced_memory()
print(f"当前内存:{current / 1024 / 1024:.2f} MB")
print(f"峰值内存:{peak / 1024 / 1024:.2f} MB")

tracemalloc.stop()

使用 sys._debugmallocstats()

调试版本的 Python 提供了内存分配统计:

1
2
3
4
import sys

# 打印内存分配统计(仅调试版本可用)
sys._debugmallocstats()

输出示例:

1
2
3
4
5
6
7
Small block threshold = 512, in 64 size classes.

class size num pools blocks available size avail
----- ---- --------- ---------------- ----------
0 8 12 12345 98760
1 16 25 6789 108624
...

⚠️ 内存优化建议

1. 避免创建大量小对象

1
2
3
4
5
6
7
8
# 低效:创建大量小元组
result = []
for i in range(1000000):
result.append((i, i+1)) # 每个元组都是独立的小对象

# 高效:使用数组或 namedtuple
from array import array
result = array('i', [i for pair in range(1000000) for i in (pair, pair+1)])

2. 使用__slots__减少内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 普通类(使用__dict__)
class Point:
def __init__(self, x, y):
self.x = x
self.y = y

# 使用__slots__(节省内存)
class EfficientPoint:
__slots__ = ['x', 'y'] # 预定义属性,不创建__dict__

def __init__(self, x, y):
self.x = x
self.y = y

# 测试内存占用
import sys
p1 = Point(1, 2)
p2 = EfficientPoint(1, 2)
print(sys.getsizeof(p1)) # 约 56 字节
print(sys.getsizeof(p2)) # 约 40 字节

slots 的原理是:普通类每个实例都有一个__dict__来存储属性,而使用__slots__后,属性直接存储在预分配的空间中,省去了__dict__的开销。

3. 对象池模式

对于需要频繁创建销毁的对象,可以实现自己的对象池:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ObjectPool:
def __init__(self, factory, max_size=100):
self.factory = factory
self.max_size = max_size
self._available = []
self._in_use = set()

def acquire(self):
if self._available:
obj = self._available.pop()
else:
obj = self.factory()
self._in_use.add(id(obj))
return obj

def release(self, obj):
if id(obj) in self._in_use:
self._in_use.remove(id(obj))
if len(self._available) < self.max_size:
self._available.append(obj)

🎯 本讲总结

通过本讲,我们深入理解了:

分层内存架构:pymalloc 负责小对象,malloc 负责大对象,各司其职。

pymalloc 三级结构:Arena(256KB)→ Pool(4KB)→ Block(8-512 字节),层层管理。

Size Class 分级:64 个大小级别,每个级别独立管理,减少碎片。

分配与释放流程:链表操作,高效快速。

内存优化技巧:避免大量小对象、使用__slots__、对象池模式等。

这些知识是理解后续垃圾回收等内容的基础。


📚 推荐教材

《Python 编程从入门到实践(第 3 版)》 - Eric Matthes 著

Python 零基础入门首选。本书分为基础语法和项目实战两部分,适合完全没有编程经验的读者。

《流畅的 Python(第 2 版)》 - Luciano Ramalho 著

Python 进阶经典之作。深入讲解 Python 的高级特性,包括数据模型、函数式编程、面向对象、元编程等。

《CPython 设计与实现》 - Anthony Shaw 著

本书深入讲解 CPython 内部机制,从内存管理到字节码执行,从对象模型到并发编程。配合本课程学习,效果更佳。

学习路线建议:

1
零基础 → 《从入门到实践》 → 《流畅的 Python》 → 本门课程 → 《CPython 设计与实现》

🔗 课程导航

上一讲:Python 对象模型深度解析 | 下一讲:垃圾回收机制详解


💬 联系我

平台账号/链接
微信扫码加好友
微博@程序员晚枫
知乎@程序员晚枫
抖音@程序员晚枫
小红书@程序员晚枫
B 站Python 自动化办公社区

主营业务:AI 编程培训、企业内训、技术咨询

🎓 AI 编程实战课程

想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!