请选择 进入手机版 | 继续访问电脑版
搜索
房产
装修
汽车
婚嫁
健康
理财
旅游
美食
跳蚤
二手房
租房
招聘
二手车
教育
茶座
我要买房
买东西
装修家居
交友
职场
生活
网购
亲子
情感
龙城车友
找美食
谈婚论嫁
美女
兴趣
八卦
宠物
手机

妻善不好欺 Python编程性能调优:使用适宜的数据结构减少对象内存消耗异世龙神录

[复制链接]
查看: 582|回复: 0

1万

主题

4万

帖子

7万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
75236
发表于 2019-7-12 14:27 | 显示全部楼层 |阅读模式
这是写在帖子头部的内容时下Python说话很是风行,由于其简单了然易于上手的特征被IT业界和非IT当,普遍利用,特别是在数据处置和AI方面更是一枝独秀。但是Python的大量的类库和利用却良莠不齐,广为诟,特别在性能方面很多法式确切存在很多题目。本文虫虫就给大师先容下若何经过利用合宜的数据结构来削减其对内存的消耗,从而优化Python利用的性能。


<h1>字典

在小法式中,出格是在剧本中,利用内置的dict来暗示结构信息很是简双方便,例如:
import sys
>>> ob = {'x':1, 'y':2, 'z':3}
>>> x = ob['x']
>>> ob['y'] = y
Python 3.6后,对dict做了优化,利用一组的更松散有序键列的实现,dict加倍便利好用。可是不要光图了利用方便,我们来看看它的内存占用。
print(sys.getsizeof(ob))


乖乖,内存占用可以不小。这还是是三个坐标而已,当法式中触及大量数据的时辰内存占用就更可怕了。我们计较一下,按照实例中工具个数和工具占用:
100w个工具的时辰 1000000*240=240Mb
1000w个工具的时辰 10000000*240=2.4Gb
<h1>类实例

另一个常用的是大量利用类,用来类属性来暗示数据结构:
class Coordinate:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
关于类的实例内存占用:
PyGC_Head 24
PyObject_HEAD 16
__weakref__ 8
__dict__ 8
总计:56
这里__weakref__是这个工具的弱援用列表的援用, __dict__是对类实例字典的援用,它包括实例属性的值(留意64位平台下援用占用8个字节)。从Python 3.3起头,同享空间用于在字典中为一切类实例存储键,这类机制削减了RAM中实例跟踪的大。


是以,大量的类实例在内存中的占用空间小于常规字典(dict):
100w类实例 1000000*56=168 Mb
100w类实例 10000000*56=1.68 Gb
虽然如此,由于实例字典的占用较大大,类实例内存占用巨细仍然很大。
<h1>带__slots__的类实例

上面我们说了,类实例中__dict__ 和__weakref__花费很多空间,假如我们把他们俩个取消便可以明显地削减RAM中类实例的巨细。而取消他们需要借助__slots__:
class Coordinate:
__slots__ = 'x', 'y', 'z'
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
ob = Point(1,2,3)


RAM中的工具巨细就变的很。
PyGC_Head 24
PyObject_HEAD 16
x 8
y 8
z 8
总计:64
在类界说中利用__slots__可以让类实例的内存占用明显削减。
100w实例 1000000*64=64 Mb
1000w实例 10000000*64=640 Mb
首要缘由是:在工具题目以后的内存中,存储了工具援用,属性值,而且利用类字典中的对特别描写符来实现对它们的拜候。要默许就用用__slots__自动建立类的进程,可以利用模块namedlist和attrs。 利用namedlist.namedlist函数建立__slots__类:
Coordinate = namedlist('Point',()'x','y','z'))
<h1>元祖

Python还有一个内置范例元组,用于暗示不成变数据结构。元组具有牢固的结构记录,但没有字段称号。对于字段拜候,利用字段索引。 在建立元组实例时,元组字段一次性与值工具关联,不能在做点窜:
ob2 =(1,2,3)
ob2[1] = 4
上面语句会报范例毛病。


元组的实例很是松散:
print(sys.getsizeof(ob2))


它们在内存中占用8个字节比利用__slots__的类实例多,由于内存中的对元组跟踪还额外引入了很多字段:
PyGC_Head 24
PyObject_HEAD 16
ob_size 8
[0] 8
[1] 8
[2] 8
总计:72
<h1>命名元祖

由于元组被普遍利用,很多人希望元祖也能经过字段称号来拜候。这也是可以的不外需要引入一个额外的模块collections.namedtuple,利用namedtuple函数自动天生这类类类:
Coordinate1 = namedtuple('Coordinate',('x','y','z'))
它建立了一个元组的子类,其中界说了用于按称号拜候字段的描写符。其行文相当于:
class Coordinate (tuple):
#
@property
def _get_x(self):
return self[0]
@property
def _get_y(self):
return self[1]
@property
def _get_y(self):
return self[2]
#
def __new__(cls, x, y, z):
return tuple.__new__(cls, (x, y, z))
此类的一切实例都具有与元组不异的内存占用。大量的实例占用很大的内存:
100w 1000000*72 72 Mb
1000w 10000000*72 Mb
<h1>Recordclass

由于元组以及响应的命名元组在ob.x的工具值不再与另一个值工具相关联的意义上天生非残缺工具,是以理论中需要可以赋值的对可变的命名元组变体的需求。由于Python中没有供给支持给元祖赋值的类似内置范例,可是有一些第三方的模块供给类似的功用:
recordclass:与类似元组的工具的巨细相比,它可以用于削减RAM中工具的巨细。
recordclass引入了范例recordclass.mutabletuple,它几近与元组不异,可是支持对其赋值。在此根本上,建立的子类几近与namedtuples完全不异,但支持为字段分派新值(不建立新实例)。recordamed类函数与thenamedtuple函数一样,支持自动建立类:
Coordinate = recordclass(' Coordinate', ('x', 'y', 'z'))
ob = Coordinate (1, 2, 3)
类实例与元组具有不异的结构,但只要一个withoutPyGC_Head:
PyObject_HEAD 16
ob_size 8
x 8
y 8
z 8
总计:48
默许情况下,recordclass函数建立一个不介入循环渣滓收集机制的类。凡是,namedtuple和recordclass用于天生暗示记录或简单数据结构的类。在Python中正确利用它们不会天生循环援用。由于这个缘由,在记录类天生的类实例以后,默许情况下,没有PyGC_Headfragment,对应于建立的类,在标志字段中,默许情况下,没有 flagPy_TPFLAGS_HAVE_GC。
大量实例的内存占用巨细比slots__的类的实例还。
100w实例 1000000*48=48 Mb
1000w实例 10000000*48=480 Mb
<h1>dataclass

recordclass供给的另一个处理计划基于以下的机制:在内存中利用与__slots__类不异的存储结构,但不介入循环渣滓收集机制。这些类经过recordclass.make_dataclass函数天生的:
Coordinate = make_dataclass('Coordinate '('x','y','z'))
默许情况下,以这类方式建立的类会建立可变实例。
另一种方式是利用来自recordclass.dataobject的继续的类声明:
class Coordinate(dataobject):
X:INT
Y:INT
Z:INT
以这类方式建立的类将建立不介入循环渣滓收集机制的实例。内存中实例的结构与__slots__的情况不异,但没有PyGC_Head:
字段大。ㄗ纸冢
PyObject_HEAD 16
X 8
Y 8
Z 8
总计:40
ob = Coordinate(1,2,3)
print(sys.getsizeof(ob))
40
要拜候这些字段,需要利用特别描写符经过工具开首的偏移量来拜候字段,这些工具位于类字典中:


这类实例的内存占用的巨细是最小的:
实例数目
100w实例 1000000*40=40 Mb
1000w实例 10000000*40=400 Mb
<h1>Cython



还有一种是基于Cython的数据成果。Cython的优点是字段可以采用C说话原子范例的值,是以效力很高。从纯Python拜候字段的描写符是自动建立的。例如:
cdef class Python:
cdef public int x, y, z
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
在这类情况下,实例的内存巨细很。
ob = Coordinate(1,2,3)
print(sys.getsizeof(ob))
32
只要32字节。在内存中的实例跟踪它具有以下结构:
PyObject_HEAD 16
x 4
y 4
z 4
总计:32
大量实例下空间占用:
100w实例 1000000*32=32 Mb
1000w实例 10000000*32=32 Mb
<h1>NumPy

对大量数据利用多维数组或记录数组可以节俭内存耗用。可是,为了在纯Python中停止高效处置,我们可以利用numpy包中的函数的处置方式。


Coordinate = numpy.dtype(('x', numpy.int32), ('y', numpy.int32), ('z', numpy.int32)])
也支持利用函数比如zeros对其停止初始化:
po = numpy.zeros(N, dtype= Coordinate)
其内存占用是最小的:
100w实例 1000000*12=12 M
1000w实例 10000000*12=120 M
对数组元素和行的一般拜候将需要从Python工具转换为C int值,反之亦然。提取单行会致使建立包括单个元素的数组。
sys.getsizeof(po[0])
68
所以,在Python代码中,倡议用numpy包中的函数处置数组。
<h1>结论

本文中,我们罗列了Python常见的数据结构及其内存的占用,基于此希望大师在开辟当挑选合适的数据结构,削减内存的利用,从而进步利用的性能。
感激您的阅读
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Copyright © 2006-2014 人才网||人才市场 网上求职|网上找工作 上潜力英才网 版权所有 法律顾问:高律师 客服电话:0791-88289918
技术支持:迪恩网络科技公司  Powered by Discuz! X3.2
快速回复 返回顶部 返回列表