python内存管理

571 阅读4分钟

1.python内存管理机制

在python中,内存管理涉及到一个包含所有python对象和数据结构的私有堆(heap)。这个私有堆的管理由内部的python内存管理器来保证。python内存管理器有不同的组件来处理各种动态存储管理方面的问题,如共享、分割、预分配或者缓存等。

在最底层,一个原始内存分配器通过与操作系统的内存管理器交互,确保私有堆中有足够的空间来存储所有与python相关的数据。在原始内存分配器,几个对象特定的分配器在同一堆上运行,并根据每种对象类型的特点实现不同的内存管理策略。例如,整数对象在堆内的管理方式不同于字符串、元组或字典,因为整数需要不同的存储需求和速度与空间的权衡。因此,python内存管理器将一些工作分配给对象特定分配器,但确保后者在私有堆的范围运行。

python堆内存的管理是由解释器来执行,用户无法控制。为了避免对于小对象(<=512bytes)出现数量过多的GC,导致的性能消耗。python对于小对象采用子分配(内存池)的方式进行内存块的管理。对于大块对象则使用标准C中的allocator来分配内存。python对于小对象的allocator由大到小分为三个层级:areana、Pool、block

1.1 Block

block是最小的一个层级,每个block仅仅可以容纳包含一个固定大小的python对象。大小8-512bytes,以8bytes为步长,分为64类不同的block

请求的字节数分配块的大小大小类idx
1-880
9-16161
17-24242
25-32323
.........
505-51251263

1.2 Pool

Pool是具有相同大小的block集合。通常情况下,Pool的大小为4kb,与虚拟内存页的大小保持一致。限制Pool中block的大小,使其固定。可以在一个对象在当前Pool中的某个block被销毁时,Pool内存管理可以将新生成的对象放入该block对象。

具有相同大小的pool通过双向链表(prevpool, nextpool字段)连接。sidx用来标识block的类型,arenaindex标识当前pool属于哪个arena,ref.count标识当前pool使用了多少个block,freeblock标识当前pools中可用block的指针。freeblock实际是单链表实现,当一块block为空时,则将该block插入到freeblock链表的头部。

每个Pool都具有三种状态:

used:部分使用。

full:满

empty:空

usedpool为了很好的高效管理Pool,python额外使用了array,usedpool来管理。usedpool按顺序存储着每个特性大小的pool的头指针,相同大小的pool按照双向链表来连接。当分配新的内存空间时,创建一个特定大小的pool,只需要使用usedpools找到头指针,遍历即可。当没有内存空间时,只需要在pool的双向链表的头部插入新的pool即可。

1.3 Arena

pools和blocks都不会直接去进行内存分配,他们都会使用从arena那边已经分配好的内存空间。arena是分配在堆上256kb的块状内存,提供了64个pool

所有的arenas也使用双链表进行连接(prevarena,nextarena字段)。nfreepools和ntotalpools存储着当前可用的pools的信息。freepools指针指向当前可用的pools。arena结构简单,职责为按需给pools分配内存。当一个arena为空时,则将该arena的内存归还给操作系统。

2.gc模块

gc 模块是我们在Python中进行内存管理的接口,一般情况Python程序员都不用关心自己程序的内存管理问题,但是有的时候,比如发现自己程序存在内存泄露,就可能需要用到gc模块的接口来排查问题。

有的 Python 系统会关闭自动垃圾回收,程序自己判断回收的时机,据说 instagram 的系统就是这样做的,整体运行效率提高了10%。

常用函数:

set_debug(flags) :设置gc的debug日志,一般设置为gc.DEBUG_LEAK可以看到内存泄漏的对象。

collect([generation]) :执行垃圾回收。会将那些有循环引用的对象给回收了。这个函数可以传递参数,0代表只回收第0代的的垃圾对象、1代表回收第0代和第1代的对象,2代表回收第0、1、2代的对象。如果不传参数,那么会使用2作为默认参数。

get_threshold() :获取gc模块执行垃圾回收的阈值。返回的是个元组,第0个是零代的阈值,第1个是1代的阈值,第2个是2代的阈值。

set_threshold(threshold0[, threshold1[, threshold2]) :设置执行垃圾回收的阈值。

get_count() :获取当前自动执行垃圾回收的计数器。返回一个元组。第0个是零代的垃圾对象的数量,第1个是零代链表遍历的次数,第2个是1代链表遍历的次数。