垃圾回收
1.1 refchain
Python的C源码中有一个refchain环形双向链表,一旦在Python程序中创建对象,就会将对象添加到refchain链表中,以保存所有对象。
name="皮卡丘" width=5
1.2引用计数器
在refchain中,所有对象都有一个ob_refcnt来保存当前对象的引用计数器
name="皮卡丘" width=5 nickname=name
上述代码表示内存中有5和“皮卡丘”两个值,其引用计数器分别为1、2
当值被多次引用时,它不会在内存中重复创建数据,而是引用计数器+1。当对象被销毁时,引用计数器-1。如果引用计数器为0,则从refchain链表中删除对象,并在内存中销毁(暂时不考虑缓存等特殊情况)。
name="皮卡丘"nickname=name#“对象”皮卡丘“引用计数器+1delname对象”"皮卡丘"引用计数器-1defrun(arg): print(arg) run(nickname)#当函数开始执行时,对象“皮卡丘”引用计数器+1,当函数完成时,对象引用计数器-1name_list=["张三","法外狂徒",name]#“皮卡丘”引用计数器+1
然而,仍然有一个bug。当出现循环引用时,一些数据将无法正常回收,例如
在v1=[11,22,33]#refchain中创建一个列表对象。由于v1=对象,列表引用对象为1. 在v2=[44、55、66]#refchain中创建另一个列表对象,因为v2=对象,所以列表对象引用的计数器为1. v1.append(v2)#如果将v2添加到v1中,则v2对应[44、55、66]对象的引用计数器添加1,最终为2. v2.append(v1)#如果将v1添加到v1中,则v1对应[11、22、33]对象的引用计数器加1,最终为2. delv1#引用计数器-1delv2#引用计数器-1delv2#
对于上述代码,在执行del操作后,没有变量将使用这两个列表对象,但由于循环引用的问题,他们的引用计数器不是0,所以他们的状态:永远不会被使用或销毁。如果项目中有太多这样的代码,它将导致内存消耗,直到内存耗尽,程序崩溃。
1.3标记清除&分代回收
标记清除:专门用于保存特殊链表 列表、元组、字典、集合、自定义等对象,然后检查链表中的对象是否有循环引用。如果存在,请双方引用计数器 - 1 。分代回收:优化标记清晰的链表,将可能存在循环引用的对象分成三个链表,链表成为0/1/2三代,每一代都可以存储对象和阈值。当达到阈值时,会扫描相应链表中的每个对象,除了循环引用分别减少1,销毁引用计数器为0的对象。`
C源代码//分代#defineNUM_GENERATIONS3structgc_generationgenerations[NUM_GENERATIONS]={ /*PyGC_Head,threshold,count*/ {{(uintptr_t)_GEN_HEAD(0),(uintptr_t)_GEN_HEAD(0)},700,0},/0代 {{(uintptr_t)_GEN_HEAD(1),(uintptr_t)_GEN_HEAD(1)},10,0},/1代 {{(uintptr_t)_GEN_HEAD(2),(uintptr_t)_GEN_HEAD(2)},10,0},/2代};
0代,count表示0代链表中对象的数量,threshold表示0代链表中对象的数量阈值,超过0代扫描检查。
第一代,count表示0代链表扫描次数,threshold表示0代链表扫描次数阈值,超过1代扫描检查。
第二代,count表示1代链表扫描次数,threshold表示1代链表扫描次数阈值,超过12代扫描检查。
1.4缓存机制
事实上,他并不是那么简单和粗糙,因为反复的创建和销毁会降低程序的执行效率。Python引入了“缓存机制”机制。例如,当引用计数器为0时,它不会真正摧毁对象,而是将其放入一个名为fre_list的链表中,然后在创建对象时不会重新打开内存,而是将以前的对象重置到fre_list中使用。
float类型,维护free_list链表最多可缓存100个float对象。
1.`v1=3.14#打开内存存储float对象,并将对象添加到refchain链表中。` 2.`print(id(v1)#内存地址:44360348` 3.`delv1#引用计数器-1,如果为0,则在rechain链表中删除,不销毁对象,而是将对象添加到floatfre_中list.` 4.`v2=9.999#优先在free_list中获取对象,并将其重置为9.999。如果fre_list是空的,请重新打开内存。` 5.`print(id(v2)#内存地址:4436033488` 7.`#注:当引用计数器为0时,首先要判断free_list中的缓存数是否满,未满时要缓存对象,已满时要直接销毁对象。`
int类型不是基于free_list,但维护small_ints链表保存常见数据(小数据池),小数据池范围:-5 <= value < 257.也就是说,当这个范围的整数被重复使用时,内存不会被重新打开。
v1=38#去小数据池small_ints获取38个整数对象,将对象添加到refchain中,让引用计数器+1。 print(id(v1)#内存地址:4514343712 v2=38#去小数据池small_ints获取38个整数对象,将refchain中的对象引用计数器+1。 print(id(v2)#内存地址:4514343712 #注意:解释器启动时,-5~256已添加到small_ints链表中,引用计数器初始化为1。当代码中使用的值直接在small_ints中使用并引用计数器+1时。此外,small_ints中的数据引用计数器永远不会为0(初始化时设置为1),因此不会被销毁。
str类型,维护unicode_latin1[256]链表,内部缓存所有ascii字符,以后使用时不会重复创建。
v1="A" print(id(v1)#输出:4517720496 delv1 v2="A" print(id(v1)#输出:4517720496 #此外,Python还对字符串进行了停留机制,对于只包含字母、数字和下划线的字符串(见源代码Objects/codeobject.c),如果存在于内存中,则不会重新创建,而是使用原始地址(不会像free_list那样存活在于内存中,只有在内存中才能重复使用)。 v1="wupeiqi" v2="wupeiqi" print(id(v1)==id(v2)#输出:True
list类型,维护free_list数组最多可缓存80个list对象。
v1=[11,22,33] print(id(v1)#输出:451762816 delv1 v2=["小猪","佩奇"] print(id(v2)#输出:451762816
tuple类型,维护free_list数组和20个数组容量,数组中的元素可以是链表,每个链表最多可以容纳2000个元组对象。在存储数据时,元组的fre_list数组根据元组可容纳的数量找到fre_list数组中对应的链表,并添加到链表中。
v1=(1,2) print(id(v1)delv1#因元组的数量为2,因此将此对象缓存到free_list链表中的[2]。 v2=("小猪","佩奇")#不会重新开放内存,而是去free_list[2]在相应的链表中使用一个对象。 print(id(v2))
dict类型,维护free_list数组最多可缓存80个dict对象。
v1={"k1":123} print(id(v1)#输出:451598128 delv1 v2={"name":"武沛齐","age":18,"gender":"男"} print(id(v1)#输出:451598128
2 C语言源码分析
2.1两个重要的结构体
#definePyObject_HEADPyObjectob_base;#definePyObject_VAR_HEADPyVarObjectob_base;//宏定义,包括前一个和下一个,用于构建双向链表。(在refchain链表中使用时)#define_PyObject_HEAD_EXTRA\ struct_object*_ob_next;\ struct_object*_ob_prev;typedefstruct_object{ _PyObject_HEAD_EXTRA//用于构建双向链表 Py_ssize_tob_refcnt;//引用计数器 struct_typeobject*ob_type;//数据类型}PyObject;typedefstruct{ PyObjectob_base;///pyobject对象 Py_ssize_tob_size;/*Numberofitemsinvariablepart,也就是说,元素个数*/}PyVarObject;
这两种结构体Pyobject和Pyvarobject是保存其他数据类型公共部分的基石。例如,每种类型的对象在创建Pyobject时都有Pyobject的四部分数据;list/setPyvarobject中的五部分数据是由多个元素组成的对象创建的/tuple。
2.2常见类型结构
通常,当我们创建一个对象时,它本质上是一个相关类型的结构,内部保存值和引用计数器。
float类型
typedefstruct{ PyObject_HEADdoubleob_fval; }PyFloatObject;
int类型
struct_longobject{ PyObject_VAR_HEAD digitob_digit[1]; };/*Long(arbitraryprecision)integerobjectinterface*/ typedefstruct_longobjectPyLongObject;/*Revealedinlongintrepr.h*/
str类型
typedefstruct{ PyObject_HEAD Py_ssize_tlength;/*Numberofcodepointsinthestring*/ Py_hash_thash;/*Hashvalue;-ifnotset*/ struct{ unsignedintinterned:2;/*Charactersize: -PyUnicode_WCHAR_KIND(0): *charactertype=wchar_t(16or32bits,dependingonthe platform) -PyUnicode__1BYTE__KIND(1): *charactertype=Py_UCS1(8bits,unsigned) *allcharactersareintherangeU+0000-U+00FF(latin1) *ifasciiisset,allcharactersareintherangeU+0000-U+007F (ASCII),otherwiseatleastonecharacterisintherange U+0080-U+00FF -PyUnicode__2BYTE___KIND(2): *charactertype=Py_UCS2(16bits,unsigned) *allcharactersareintherangeU+0000-U+FFFF(BMP) *atleastonecharacterisintherangeU+0100-U+FFFF -PyUnicode_4BYTE___KIND(4): *charactertype=Py_UCS4(32bits,unsigned) *allcharactersareintherangeU+0000-U+10FFFF *atleastonecharacterisintherangeU+10000-U+10FFFF */ unsignedintkind:3;unsignedintcompact:1;unsignedintascii:1;unsignedintready:1;unsignedint:24; }state;wchar_t*wstr;/*wchar_trepresentation(null-terminated)*/ }PyASCIIObject;typedefstruct{ PyASCIIObject_base; Py_ssize_tutf8__length;/*Numberofbytesinutf8,excludingthe *terminating\0.*/ char*utf8;/*UTF-8representation(null-terminated)*/ Py_ssize_twstr_length;/*Numberofcodepointsinwstr,possible *surrogatescountastwocodepoints.*/ }PyCompactUnicodeObject;typedefstruct{ PyCompactUnicodeObject_base;union{void*any; Py_UCS1*latin1; PyUCS2*ucs2; PyUCS4*ucs4; }data;/*Canonical,smallest-formUnicodebuffer*/ }PyUnicodeObject;
list类型
typedefstruct{ PyObject_VAR_HEAD PyObject**ob_item; Py_ssize_tallocated; }PyListObject;
tuple类型
typedefstruct{ PyObject_VAR_HEAD PyObject*ob_item[1]; }PyTupleObject;
dict类型
typedefstruct{ PyObject_HEAD Py_ssize_tma_used; PyDictKeysObject*ma_keys; PyObject**ma_values; }PyDictObject;
基本上,每个对象内存储的数据都可以通过常见的结构体来理解。
扩展:在结构部分,你应该发现str类型比较繁琐,因为python字符串在处理时需要考虑编码,内部规定(见源代码结构):
如果字符串只包含ascii,则每个字符用一个字节表示,即:latin1字符串包含中文等,则每个字符用两个字节表示:ucs2字符串包含emoji等,则每个字符用四个字节表示:ucs4