小对象碎片是pymalloc正常行为,需干预反复创建销毁、尺寸混杂且未复用的小对象;list/dict循环新建加剧内部碎片;__slots__可降自定义类碎片但对内置类型无效;RSS持续上涨而活跃对象稳定提示外部碎片。

直接说结论:小对象碎片不是“泄露”,而是 pymalloc 的正常行为;真正要干预的,是那些反复创建销毁、尺寸混杂、又没被复用的小对象。
为什么 list 和 dict 在循环里反复新建会加剧碎片
CPython 对小于 512 字节的对象走 pymalloc,按固定块大小(8B/16B/…/512B)切分 pool。当你在循环里写 my_dict = {} 或 items = [],每次都会从对应 size class 的 pool 里取一个 block。如果这些容器后续只存少量数据(比如平均 3 个键值对),实际只用了 16B 块中的一小部分,剩下空间就成内部碎片;更糟的是,若循环中混合创建不同尺寸对象(如一会儿 12 字符 str,一会儿 20 字符 str),它们可能落在同一 pool 但无法合并空闲块。
- 避免写
for i in range(n): data = {}; data['x'] = i,改用data.clear()复用已有 dict - 初始化时预估容量:
my_list = [None] * expected_size,比动态 append 更少触发 resize 和新 block 分配 - 字符串拼接不用
result += s,它每轮都生成新 str 对象;改用''.join(parts)或io.StringIO
__slots__ 真的能降低碎片?怎么用才有效
能,但只对自定义类实例生效。Python 默认给每个实例挂一个 __dict__(底层是 dict),而 dict 本身由多个小对象(entry、key hash、value 引用等)组成,频繁增删属性就会在 pymalloc 中留下大量零散 block。__slots__ 把属性固化为固定偏移的 C 结构体字段,去掉 __dict__,单个实例内存开销下降 30–50%,且不再触发 dict 扩容相关的碎片链。
图片提示词生成器?不止如此。 马甲系统 —— 把脑海中的画面,翻译成AI能理解的专业表达。 用得越多,它越懂你:首次需要多问几句确认方向,用久了几乎一说就懂。 用得越多,它越快:缓存机制让后续对话越来越省。 RAG进化:成功案例持续入库,越跑越聪明。 输入「新手指南」查看完整功能介绍
- 必须显式声明:
class Worker: __slots__ = ('name', 'pid', 'status') - 不支持动态添加属性,所以别在
__slots__类里用obj.new_field = 1 - 继承链中只要父类用了
__slots__,子类也得声明,否则子类仍会生成__dict__ - 对内置类型(
list/dict/str)无效——它们没__slots__,也不能加
什么时候该怀疑是 pymalloc 外部碎片,而不是代码问题
现象是:RSS(常驻内存)持续上涨,但 tracemalloc 显示活跃对象总量没变、gc.get_objects() 返回对象数稳定、sys.getsizeof() 算出的总内存远小于 RSS。这说明 pymalloc 的 arena 没归还 OS —— 它还在池里等着复用,但你的 workload 已经切换到其他 size class,旧 arena 就卡住了。
立即学习“Python免费学习笔记(深入)”;
- 用
psutil.Process().memory_info().rss监控 RSS,和tracemalloc.get_traced_memory()对比,差值 > 50MB 且长期不降,就是典型外部碎片信号 - 临时缓解:调用
gc.collect(2)强制清理老年代,有时能促使 pymalloc 归还空 arena - 长期方案:升级到 Python 3.13+ 并启用
mimalloc(编译时加--with-mimalloc),它对 arena 合并和跨 size class 复用更激进 - 别依赖
gc.disable()来“提速”——它会让所有未回收对象滞留在池里,碎片雪球越滚越大
碎片不是 bug,是 pymalloc 在吞吐和内存返还之间做的权衡;你真正能控制的,是对象的生命周期模式——让分配更可预测、复用更彻底、尺寸更单一。

















