百摩网
当前位置: 首页 生活百科

python 内存用量(如何降低Python的内存消耗量)

时间:2023-07-16 作者: 小编 阅读量: 1 栏目名: 生活百科

在程序执行期间,如果内存中存在大量处于活动状态的对象,就有可能出现内存问题,尤其是在可用内存总量有限的情况下。在本文中,我们将讨论通过缩小对象大幅减少Python所需内存量的方法。从Python3.3开始,所有类实例的字典的键都存储在共享空间中。元组Python还有一个自带的元组类型,代表不可修改的数据结构。元组是固定的结构或记录,但它不包含字段名称。由于Python没有相当于元组且支持赋值的内置类型,因此人们想了许多办法。

在程序执行期间,如果内存中存在大量处于活动状态的对象,就有可能出现内存问题,尤其是在可用内存总量有限的情况下。在本文中,我们将讨论通过缩小对象大幅减少Python所需内存量的方法。

作者 | intellimath

译者 | 弯月,责编 | 郭芮

出品 | CSDN(ID:CSDNnews)

以下为译文:

为了简便起见,我们以一个表示点的Python结构为例,它包括x、y、z坐标值,坐标值可以通过名称访问。

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 000

240 Mb

10 000 000

2.40 Gb

100 000 000

24 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_Head

24

PyObject_HEAD

16

__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 000

168 Mb

10 000 000

1.68 Gb

100 000 000

16.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_Head

24

PyObject_HEAD

16

x

8

y

8

z

8

总计:

64

在类的定义中使用了__slots__以后,大量实例占据的内存就明显减少了:

实例数

大小

1 000 000

64 Mb

10 000 000

640 Mb

100 000 000

6.4 Gb

目前,这是降低类实例占用内存的主要方式。

这种方式减少内存的原理为:在内存中,对象的标题后面存储的是对象的引用(即属性值),访问这些属性值可以使用类字典中的特殊描述符:

>>> pprint(Point.__dict__)mappingproxy(....................................'x': <member 'x' of 'Point' objects>,'y': <member 'y' of 'Point' objects>,'z': <member 'z' of 'Point' objects>})

为了自动化使用__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_Head

24

PyObject_HEAD

16

ob_size

8

[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 000

72 Mb

10 000 000

720 Mb

100 000 000

7.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_HEAD

16

ob_size

8

x

8

y

8

z

8

总计:

48

在默认情况下,recordclass函数会创建一个类,该类不参与垃圾回收机制。一般来说,namedtuple和recordclass都可以生成表示记录或简单数据结构(即非递归结构)的类。在Python中正确使用这二者不会造成循环引用。因此,recordclass生成的类实例默认情况下不包含PyGC_Head片段(这个片段是支持循环垃圾回收机制的必需字段,或者更准确地说,在创建类的PyTypeObject结构中,flags字段默认情况下不会设置Py_TPFLAGS_HAVE_GC标志)。

大量实例占用的内存量要小于带有__slots__的类实例:

实例数

大小

1 000 000

48 Mb

10 000 000

480 Mb

100 000 000

4.8 Gb

dataobject

recordclass库提出的另一个解决方案的基本想法为:内存结构采用与带__slots__的类实例同样的结构,但不参与循环垃圾回收机制。这种类可以通过recordclass.make_dataclass函数生成:

>>> Point = make_dataclass('Point', ('x', 'y', 'z'))

这种方式创建的类默认会生成可修改的实例。

另一种方法是从recordclass.dataobject继承:

class Point(dataobject):x:inty:intz:int

这种方法创建的类实例不会参与循环垃圾回收机制。内存中实例的结构与带有__slots__的类相同,但没有PyGC_Head:

字段

大小(字节)

PyObject_HEAD

16

ob_size

8

x

8

y

8

z

8

总计:

48

>>> ob = Point(1,2,3)>>> print(sys.getsizeof(ob))40

如果想访问字段,则需要使用特殊的描述符来表示从对象开头算起的偏移量,其位置位于类字典内:

mappingproxy({'__new__': <staticmethod at 0x7f203c4e6be0>,.......................................'x': <recordclass.dataobject.dataslotgetset at 0x7f203c55c690>,'y': <recordclass.dataobject.dataslotgetset at 0x7f203c55c670>,'z': <recordclass.dataobject.dataslotgetset at 0x7f203c55c410>})

大量实例占用的内存量在CPython实现中是最小的:

实例数

大小

1 000 000

40 Mb

10 000 000

400 Mb

100 000 000

4.0 Gb

Cython

还有一个基于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

内存结构如下:

字段

大小(字节)

PyObject_HEAD

16

x

4

y

4

z

4

nycto

4

总计:

32

大量副本所占用的内存量也很小:

实例数

大小

1 000 000

32 Mb

10 000 000

320 Mb

100 000 000

3.2 Gb

但是,需要记住在从Python代码访问时,每次访问都会引发int类型和Python对象之间的转换。

numpy

使用拥有大量数据的多维数组或记录数组会占用大量内存。但是,为了有效地利用纯Python处理数据,你应该使用Numpy包提供的函数。

>>> Point = numpy.dtype(('x', numpy.int32), ('y', numpy.int32), ('z', numpy.int32)])

一个拥有N个元素、初始化成零的数组可以通过下面的函数创建:

>>> points = numpy.zeros(N, dtype=Point)

内存占用是最小的:

实例数

大小

1 000 000

12 Mb

10 000 000

120 Mb

100 000 000

1.2 Gb

一般情况下,访问数组元素和行会引发Python对象与C语言int值之间的转换。如果从生成的数组中获取一行结果,其中包含一个元素,其内存就没那么紧凑了:

>>> sys.getsizeof(points[0])68

因此,如上所述,在Pytho代码中需要使用numpy包提供的函数来处理数组。

总结

在本文中,我们通过一个简单明了的例子,求证了Python语言(CPython)社区的开发人员和用户可以真正减少对象占用的内存量。

原文:https://habr.com/en/post/458518/

本文为 CSDN 翻译,转载请注明来源出处。

【END】

    推荐阅读
  • 水产养殖肥水肥料配方 水产养殖肥水剂配方

    近几年来,由于草鱼的行情不稳,加上成本很高,很多养殖老板由以草鱼为主的养殖模式逐步转变。下面为大家整理水产养殖肥水肥料配方如下:纯净水600~800份,红糖100~120份,鲜鸡血30~40份,,柠檬酸钠60~80份,腐殖酸100~120份,芽孢杆菌200~220份,硫酸镁50~60份,硫酸锌20~30份,氨基酸液80~100份,明矾50~60份,大蒜油50~60份,EM菌100~120份。

  • 泥炭土的自制方法(泥炭土的制作过程)

    泥炭土是不能自制的。想要用泥炭土来养花,可直接去市场购买使用。泥炭土是一项重要的有机物质资源,只需轻度加工就可作为土壤改良材料。泥炭土是在某些低平原及山间谷地中,由于长时间积水的原因,一些水生植生长十分茂密,出现缺氧的情况,从而会大量分解不充分的植物残体,积累并形成泥炭层的土壤。另外,像百合、酸浆草这些球根植物,也可以使用泥炭土养护。泥炭土不适合养球根易烂的植物,比如大丽花、郁金香、风信子等。

  • 石榴移植后怎么种植(移栽石榴树苗在家养一盆)

    花花最喜欢吃的水果之一就是红石榴,拿到一个大大的红石榴,不仅果汁饱满,而且又大颗又美味,在市场上买几个石榴就要十几块。想要吃的花友们,为了能够吃到红石榴,只能自己动手丰衣足食了。

  • 明末清初的赣南总兵是什么官(曾代表闽西古代官员品级的)

    不久前,因参加“研讨会”等有关学术活动,到了闽西几个传统村落参观,发现了一些村落都有“大夫第”建筑。“大夫第”在闽西地区,似乎并不稀少,但各地对于“大夫第”的解说,却不尽相同。相当一部分是上述“名誉官员”的住宅。但不管怎么说,“大夫第”的主人,是有身份、有地位、有文化品位的人,这一点是不假的。当地有关介绍说他相当于“省教育厅长”,这其实是错误的。

  • 感情说说很现实的句子(句句精湛)

    爱太满了,对他而言不是幸福,而是负担。月满则亏,水满则溢,有时,太多的爱不是爱,而是巨大的伤害。每个人都会犯错。宽恕只与爱的深度有关。喜欢某人,并不一定要成为恋人。有时候,能做朋友就已足够;唯有如此,才能长久。友情进一步是爱情,爱情退一步却回不到朋友。真正爱一个人,很多事情是会情不自禁的。一个人爱不爱你,在不在意你,你是感觉得到的。不要骗自己,不要勉强自己。

  • 3月2号是什么星座(3月2号的星座)

    下面希望有你要的答案,我们一起来看看吧!双鱼座是黄道宫上最后一个星座,主宰星为海王星,注重心灵,非常感性。太阳落在双鱼座的你,性格上体贴、思想脱俗、多才多艺,身上有一种令人难以抗拒的奇异的魅力。你极具浪漫情怀,在知性与感性的冲击下,往往能成为一个无与伦比的艺术家。生性善解人意、坦诚而迷人,喜欢罗曼蒂克的感觉。而且,极富同情心,乐于助人,喜欢奉献,不会随意伤人,看不得他人受到伤害,以及痛苦的表情。

  • 容貌焦虑是什么意思(容貌焦虑的特征有什么)

    跟着小编一起来看一看吧!容貌焦虑是什么意思容貌焦虑,是指在放大颜值作用的环境下,很多人对于自己的外貌不够自信,觉得自己不好看而产生的焦虑感。容貌之所以受到重视,还是因为当今社会对女性的宽容度越来越低。容貌焦虑的表现如下:总觉得自己不好看,会不断照镜子这样的重复行为来确认自己的外貌。有想过整形去调整自己的面部、身材。变得不自信,害怕被人盯着看,社交表现得很怯弱。

  • 煮咖啡的方法(煮咖啡的方法是什么)

    以下内容希望对你有帮助!煮咖啡的方法材料:咖啡豆适量、纯净水半瓶、砂糖一小袋、牛奶适量。准备好要用的材料。取适量咖啡豆,放入手动咖啡研磨机内,根据需要咖啡粉的粗细调整磨芯。在摩卡壶底座加入适量水,并低于安全阀,把磨好的咖啡粉倒入咖啡粉漏斗。取一张滤纸沾湿,粘在咖啡萃取壶的底端。放在电磁炉上烧开。烧开后倒入咖啡杯,按个人喜好加糖和牛奶。

  • 咳嗽严重导致尿失禁(打喷嚏会漏尿怎么办)

    而女性尿道周围除了盆底肌层外,没有其它增大尿道压力的组织和器官。一旦年龄增大,盆底功能下降,极易发生不能控制的尿液流出。少尿24小时,尿量少于300ml。尿量少可能由于喝水少、排汗多、肾功能衰竭引起。主要原因和分娩损伤、绝经后雌激素减退导致的盆底功能下降。因为和分娩、更年期有关,因此长期以来为妇产科医生所重视,也列为妇产科疾病。擅长女性月经疾病、不孕症、多囊卵巢综合征、子宫内膜异位症等疾病的诊治。

  • 水晶泥对小孩的危害(7岁女童DIY水晶泥时灼伤角膜)

    近年来,国家有关监管部门曾多次发文提醒水晶泥的危害,但在各大电商平台,水晶泥的销售仍然十分火爆。昨日,江城专家提醒,水晶泥中含有可能危害健康的物质硼砂,家长尽可能不要买给孩子当玩具。儿童玩软泥玩具后应及时洗手,避免黏性物质长时间粘在手上,刺激皮肤,防止有害物质进入体内,一旦出现误食,应立即就医。