1 介绍
使用Python语言的一个好处是Python和其它一些高级语言一样,会进行自动的内存管理。它使用引用计数机制检测为对象分配的内存是否可以被释放。然而,在Python中内存永远不会还给操作系统,Python会持有这些内存并在需要时重新使用它们。在很多场景下,这个特性可以减少内存申请和释放所带来的性能损耗;但对于需要长时间运行的Python进程来讲,Python将会占用大量的内存。如果进程使用内存的峰值远大于平均值,这将会造成内存的浪费从而影响本进程甚至是系统中其它进程的性能。
下面我们首先了解一下,Python是如何进行内存进行管理的。
2 Pymalloc
Python使用Pymalloc管理内存。在Python中,会频繁的创建和删除很多小对象,如果这些对象的内存申请和释放都使用malloc()和free(),将会带来严重的性能问题。因此,Pymalloc分配一系列256KB的内存块,称之为arena。每个arena分割为4KB大小的内存池Pool,每个Pool再切分为固定大小的Block。在内存分配时,分配给进程的就是这些Blocks。
2.1 内存分配
上图中展示了一个usedpool数组,此数组按内存大小组织,每个大小对应一个Pool链表,每个Pool链表中有多个空闲的Block。在分配内存时,Pymalloc先判断是否存在要申请的大小的Pool,如果存在的话,直接从Pool中获取一个Free Block返回给应用程序,这个过程是非常迅速的。如果分配完这个Block后此Pool变为一个空Pool,则将这个Pool从链表中移除。
如果在usedpool中找不到大小匹配的Pool,需要在freepool中查找可用的Pool。在找不到的情况下,首先会尝试在最后一个arena中看是否存在可用的内存,如果有的话则分配一个给freepool使用;如果不存在这样的arena,将会通过malloc()分配一个新的arena。在freepool中找到一个可用的Pool后,会将此Pool切分为固定大小的Pool并加入到usedpool中,并在其中分配一个Free Block给应用程序。
2.2 内存释放
在应用程序要释放一个Block时,过程和分配的过程比较相似。首先会根据Block找到此Block所归属的Pool,然后将此Block加入到Pool的Free Block列表中。如果Pool当前是空的,还会将这个Pool加入到usedpool的链表中。如果在Block加入到Free Block后Pool中所有的Block都是Free的,会将此Pool从usedpool移动到freepool中。
上面的过程可以看到,内存释放的过程基本就是内存申请的反过程,但唯一的区别是缺少了将freepool返还给arena,并将arena通过free()返还给操作系统的步骤。
3 基本数据类型的内存分配
Python中有一小部分的对象是不使用Pymalloc进程内存分配的,主要是integer/float/list/dict。为了提升这些常用对象的内存使用效率,这些对象是保存在单独的列表中的。
Python通过malloc()为Integer/Float两种类型分配大约1KB大小的内存块列表,这些列表被当作Integer/Float的数组使用,而不是使用Pymalloc的分配的8字节的整数倍大小的Block,以减少内存消耗。在创建一个新的Integer/Float对象时,直接从这个内存列表中获取数据,或是重新分配一块新的Block;在释放时对应的Block重新加入到列表中。这些Block也是不会被返还给操作系统的。
Python为List/Dict采用不同的策略,会最多保留80个空闲的List/Dict,如果多余80个,多出的会被释放。
上面的从网上的一个英文资料中翻译节选的,原文请参考:http://www.evanjones.ca/memoryallocator/