Python 在内存管理上做了一系列优化,避免频繁向操作系统申请和释放小片内存,从而提高程序执行效率。这些优化对开发者大部分时候是透明的,但了解它们能帮助你避开一些不易察觉的坑,也能在排查内存问题时更有方向。
小整数对象池
Python 在启动时会预先创建好从 -5 到 256 的所有整数对象,放入一个全局池中。程序中任何地方用到这个范围内的整数,都会直接复用池中已有的对象,而不会创建新对象。
a = 100
b = 100
print(a is b) # True,因为 100 在 -5~256 之间,指向同一个对象
x = 300
y = 300
print(x is y) # False,300 超出范围,每次赋值都会创建新的整数对象
设计原因:小整数被频繁使用(如循环索引、简单计数),预创建对象可以大幅减少内存分配与回收的额外开销,提高执行速度。
实际注意点:
- 判断两个整数是否值相等一定要用
==,不要依赖is。小整数池的行为是 CPython 的实现细节,跨解释器、跨版本未必一致。 - 写代码时不必刻意利用这个机制,编译器/解释器的优化比你手工控制更可靠。
字符串驻留
Python 会自动对一些字符串执行驻留,即只保存一份副本,相同内容的字符串引用同一个对象。主要针对看起来像“标识符”的字符串:仅包含字母、数字、下划线,且长度较短。
s1 = "hello"
s2 = "hello"
print(s1 is s2) # 通常为 True,因为 "hello" 符合标识符条件
s3 = "hello world"
s4 = "hello world"
print(s3 is s4) # 通常为 False,包含空格,不会被自动驻留
s5 = "hello!"
s6 = "hello!"
print(s5 is s6) # 通常为 False,包含叹号,不会被自动驻留
你也可以通过 sys.intern() 手动强制驻留一个字符串,让后续相同字符串的引用都指向同一对象,这在需要大量重复字符串比较的场景里能有效节省内存并加速。
设计原因:变量名、属性名、字典键等经常出现大量重复字符串,驻留可以显著降低内存占用量。
实际注意点:
- 和整数一样,字符串内容比较永远用
==,不要用is判断字符串是否相等。 - 自动驻留的范围是 CPython 实现细节,Jython 或 PyPy 可能行为不同。
pymalloc 内存分配器
CPython 内部实现了一套专为 Python 对象设计的私有内存分配器,名为 pymalloc。它的核心思路是:对小对象(小于 512 字节)进行分层管理,减少直接调用 C 的 malloc 和 free,从而提升分配效率和空间利用率。
pymalloc 采用三级层次结构:
- Arena(区域):大块连续内存,每次向操作系统申请 256KB。
- Pool(池):从 Arena 中切出,每个 4KB,专用于处理同一种大小的对象。
- Block(块):池内部再切分为固定大小的块,直接存放 Python 对象。
当需要创建新对象时,pymalloc 先从对应大小的池中取空闲块;对象销毁时,块被归还到池中,并不立即交还操作系统。这样,频繁创建和销毁小对象(如列表元素、临时变量)时,大部分操作只在用户态完成,无需系统调用,效率极高。
超过 512 字节的对象(如大列表、长字符串)则直接使用 C 的 malloc 分配,由系统内存管理机制控制。
设计原因:Python 程序运行时会瞬间产生大量小对象(每个整数、每次函数调用都会分配对象),借助 pymalloc 可以减轻内存碎片和分配时延。
实际影响与注意点:
- 内存占用“虚高”:一个 Python 进程的内存使用量看起来可能很大,但实际活跃对象并不多。这是因为 pymalloc 不会立即将空闲内存归还给操作系统,而是留作后续使用。这对长时间运行的服务器进程是正常的,不属于内存泄漏。
- 排查内存问题时:需要区分“Python 内部保留”和“真实泄漏”。使用
tracemalloc等工具才能定位哪些对象仍然存活。 - 多线程场景:pymalloc 会为每个线程分配独立的 Arena 池,避免锁竞争。
总体而言,Python 的内存池机制让日常开发几乎不用关心内存分配细节,开发效率因此而提高。只有在遇到内存使用异常或进行极致优化时,才需要深入理解这些底层行为。