如何释放Python占用的内存

Python020

如何释放Python占用的内存,第1张

象的引用计数减少;

函数运行结束,所有局部变量都被销毁,对象的引用计数也就随之减少。例如 foo(x) 运行结束,x 被销毁;

当变量被赋值给另一个对象时,原对象的引用计数也会减少。例如 x = 4,这时候 3 这个对象的引用计数就减 1 了;

使用 del 删除一个变量也会导致对象引用减少。例如 del x;

对象从集合对象中移除。例如 lst.remove(x);

包含对象的集合对象被销毁。例如 del lst;

这些操作都可能使对象变成垃圾回收对象,由垃圾收集器负责收集,当然垃圾收集器也负责处理循环引用对象。

要立即释放,可以使用下面的代码

import gc

gc.collect()

Dict在小型程序中,特别是在脚本中,使用Python自带的dict来表示结构信息非常简单方便:>>>ob = {'x':1, 'y':2, 'z':3}>>>x = ob['x']>>>ob['y'] = y由于在Python 3.6中dict的实现采用了一组有序键,因此其结构更为紧凑,更深得人心。但是,让我们看看dict在内容中占用的空间大小:>>>print(sys.getsizeof(ob))240如上所示,dict占用了大量内存,尤其是如果突然虚需要创建大量实例时:实例数对象大小1 000 000240 Mb10 000 0002.40 Gb100 000 00024 Gb类实例有些人希望将所有东西都封装到类中,他们更喜欢将结构定义为可以通过属性名访问的类:class Point:#def __init__(self, x, y, z):self.x = xself.y = yself.z = z>>>ob = Point(1,2,3)>>>x = ob.x>>>ob.y = y类实例的结构很有趣:字段大小(比特)PyGC_Head24PyObject_HEAD16__weakref__8__dict__8合计:56在上表中,__weakref__是该列表的引用,称之为到该对象的弱引用(weak reference);字段__dict__是该类的实例字典的引用,其中包含实例属性的值(注意在64-bit引用平台中占用8字节)。从Python3.3开始,所有类实例的字典的键都存储在共享空间中。这样就减少了内存中实例的大小:>>>print(sys.getsizeof(ob), sys.getsizeof(ob.__dict__))56 112因此,大量类实例在内存中占用的空间少于常规字典(dict):实例数大小1 000 000168 Mb10 000 0001.68 Gb100 000 00016.8 Gb不难看出,由于实例的字典很大,所以实例依然占用了大量内存。带有__slots__的类实例为了大幅降低内存中类实例的大小,我们可以考虑干掉__dict__和__weakref__。为此,我们可以借助 __slots__:class Point:__slots__ = 'x', 'y', 'z'def __init__(self, x, y, z):self.x = xself.y = yself.z = z>>>ob = Point(1,2,3)>>>print(sys.getsizeof(ob))64如此一来,内存中的对象就明显变小了:字段大小(比特)PyGC_Head24PyObject_HEAD16x8y8z8总计:64在类的定义中使用了__slots__以后,大量实例占据的内存就明显减少了:实例数大小1 000 00064 Mb10 000 000640 Mb100 000 0006.4 Gb目前,这是降低类实例占用内存的主要方式。这种方式减少内存的原理为:在内存中,对象的标题后面存储的是对象的引用(即属性值),访问这些属性值可以使用类字典中的特殊描述符:>>>pprint(Point.__dict__)mappingproxy(....................................'x': ,'y': ,'z': })为了自动化使用__slots__创建类的过程,你可以使用库namedlist(https://pypi.org/project/namedlist)。namedlist.namedlist函数可以创建带有__slots__的类:>>>Point = namedlist('Point', ('x', 'y', 'z'))还有一个包attrs(https://pypi.org/project/attrs),无论使用或不使用__slots__都可以利用这个包自动创建类。元组Python还有一个自带的元组(tuple)类型,代表不可修改的数据结构。元组是固定的结构或记录,但它不包含字段名称。你可以利用字段索引访问元组的字段。在创建元组实例时,元组的字段会一次性关联到值对象:>>>ob = (1,2,3)>>>x = ob[0]>>>ob[1] = y # ERROR元组实例非常紧凑:>>>print(sys.getsizeof(ob))72由于内存中的元组还包含字段数,因此需要占据内存的8个字节,多于带有__slots__的类:字段大小(字节)PyGC_Head24PyObject_HEAD16ob_size8[0]8[1]8[2]8总计:72命名元组由于元组的使用非常广泛,所以终有一天你需要通过名称访问元组。为了满足这种需求,你可以使用模块collections.namedtuple。namedtuple函数可以自动生成这种类:>>>Point = namedtuple('Point', ('x', 'y', 'z'))如上代码创建了元组的子类,其中还定义了通过名称访问字段的描述符。对于上述示例,访问方式如下:class Point(tuple):#@propertydef _get_x(self):return self[0]@propertydef _get_y(self):return self[1]@propertydef _get_z(self):return self[2]#def __new__(cls, x, y, z):return tuple.__new__(cls, (x, y, z))这种类所有的实例所占用的内存与元组完全相同。但大量的实例占用的内存也会稍稍多一些:实例数大小1 000 00072 Mb10 000 000720 Mb100 000 0007.2 Gb记录类:不带循环GC的可变更命名元组由于元组及其相应的命名元组类能够生成不可修改的对象,因此类似于ob.x的对象值不能再被赋予其他值,所以有时还需要可修改的命名元组。由于Python没有相当于元组且支持赋值的内置类型,因此人们想了许多办法。在这里我们讨论一下记录类(recordclass,https://pypi.org/project/recordclass),它在StackoverFlow上广受好评(https://stackoverflow.com/questions/29290359/existence-of-mutable-named-tuple-in)。此外,它还可以将对象占用的内存量减少到与元组对象差不多的水平。recordclass包引入了类型recordclass.mutabletuple,它几乎等价于元组,但它支持赋值。它会创建几乎与namedtuple完全一致的子类,但支持给属性赋新值(而不需要创建新的实例)。recordclass函数与namedtuple函数类似,可以自动创建这些类:>>>Point = recordclass('Point', ('x', 'y', 'z'))>>>ob = Point(1, 2, 3)类实例的结构也类似于tuple,但没有PyGC_Head:字段大小(字节)PyObject_HEAD16ob_size8x8y8z8总计:48在默认情况下,recordclass函数会创建一个类,该类不参与垃圾回收机制。一般来说,namedtuple和recordclass都可以生成表示记录或简单数据结构(即非递归结构)的类。在Python中正确使用这二者不会造成循环引用。因此,recordclass生成的类实例默认情况下不包含PyGC_Head片段(这个片段是支持循环垃圾回收机制的必需字段,或者更准确地说,在创建类的PyTypeObject结构中,flags字段默认情况下不会设置Py_TPFLAGS_HAVE_GC标志)。大量实例占用的内存量要小于带有__slots__的类实例:实例数大小1 000 00048 Mb10 000 000480 Mb100 000 0004.8 Gbdataobjectrecordclass库提出的另一个解决方案的基本想法为:内存结构采用与带__slots__的类实例同样的结构,但不参与循环垃圾回收机制。这种类可以通过recordclass.make_dataclass函数生成:>>>Point = make_dataclass('Point', ('x', 'y', 'z'))这种方式创建的类默认会生成可修改的实例。另一种方法是从recordclass.dataobject继承:class Point(dataobject):x:inty:intz:int这种方法创建的类实例不会参与循环垃圾回收机制。内存中实例的结构与带有__slots__的类相同,但没有PyGC_Head:字段大小(字节)PyObject_HEAD16ob_size8x8y8z8总计:48>>>ob = Point(1,2,3)>>>print(sys.getsizeof(ob))40如果想访问字段,则需要使用特殊的描述符来表示从对象开头算起的偏移量,其位置位于类字典内:mappingproxy({'__new__': ,.......................................'x': ,'y': ,'z': })大量实例占用的内存量在CPython实现中是最小的:实例数大小1 000 00040 Mb10 000 000400 Mb100 000 0004.0 GbCython还有一个基于Cython(https://cython.org/)的方案。该方案的优点是字段可以使用C语言的原子类型。访问字段的描述符可以通过纯Python创建。例如:cdef class Python:cdef public int x, y, zdef __init__(self, x, y, z):self.x = xself.y = yself.z = z本例中实例占用的内存更小:>>>ob = Point(1,2,3)>>>print(sys.getsizeof(ob))32内存结构如下:字段大小(字节)