Python占用的内存优化教程


Posted in Python onJuly 28, 2019

概述

如果程序处理的数据比较多、比较复杂,那么在程序运行的时候,会占用大量的内存,当内存占用到达一定的数值,程序就有可能被操作系统终止,特别是在限制程序所使用的内存大小的场景,更容易发生问题。下面我就给出几个优化Python占用内存的几个方法。

说明:以下代码运行在Python3。

举个栗子

我们举个简单的场景,使用Python存储一个三维坐标数据,x,y,z。

Dict

使用Python内置的数据结构Dict来实现上述例子的需求很简单。

>>> ob = {'x':1, 'y':2, 'z':3}
>>> x = ob['x']
>>> ob['y'] = y

查看以下ob这个对象占用的内存大小:

>>> print(sys.getsizeof(ob))
240

简单的三个整数,占用的内存还真不少,想象以下,如果有大量的这样的数据要存储,会占用更大的内存。

数据量 占用内存大小
1 000 000 240 Mb
10 000 000 2.40 Gb
100 000 000 24 Gb

Class

对于喜欢面向对象编程的程序员来说,更喜欢把数据包在一个class里。使用class使用同样需求:

class Point:
 #
 def __init__(self, x, y, z):
 self.x = x
 self.y = y
 self.z = z

>>> ob = Point(1,2,3)

class的数据结构和Dict区别就很大了,我们来看看这种情况下占用内存的情况:

字段 占用内存
PyGC_Head 24
PyObject_HEAD 16
__weakref__ 8
__dict__ 8
TOTAL 56

关于 __weakref__(弱引用)可以查看这个文档, 对象的dict中存储了一些self.xxx的一些东西。从Python 3.3开始,key使用了共享内存存储, 减少了RAM中实例跟踪的大小。

>>> print(sys.getsizeof(ob), sys.getsizeof(ob.__dict__)) 
56 112

数据量 占用内存
1 000 000 168 Mb
10 000 000 1.68 Gb
100 000 000 16.8 Gb

可以看到内存占用量,class比dict少了一些,但这远远不够。

__slots__

从class的内存占用分布上,我们可以发现,通过消除dict和_weakref__,可以显着减少RAM中类实例的大小,我们可以通过使用slots来达到这个目的。

class Point:
 __slots__ = 'x', 'y', 'z'

 def __init__(self, x, y, z):
 self.x = x
 self.y = y
 self.z = z

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

可以看到内存占用显著的减少了

字段 内存占用
PyGC_Head 24
PyObject_HEAD 16
x 8
y 8
z 8
TOTAL 64

数据量 占用内存
1 000 000 64Mb
10 000 000 640Mb
100 000 000 6.4Gb

默认情况下,Python的新式类和经典类的实例都有一个dict来存储实例的属性。这在一般情况下还不错,而且非常灵活,乃至在程序中可以随意设置新的属性。但是,对一些在”编译”前就知道有几个固定属性的小class来说,这个dict就有点浪费内存了。

当需要创建大量实例的时候,这个问题变得尤为突出。一种解决方法是在新式类中定义一个slots属性。

slots声明中包含若干实例变量,并为每个实例预留恰好足够的空间来保存每个变量;这样Python就不会再使用dict,从而节省空间。

那么用slot就是非非常那个有必要吗?使用slots也是有副作用的:

  1. 每个继承的子类都要重新定义一遍slots
  2. 实例只能包含哪些在slots定义的属性,这对写程序的灵活性有影响,比如你由于某个原因新网给instance设置一个新的属性,比如instance.a = 1, 但是由于a不在slots里面就直接报错了,你得不断地去修改slots或者用其他方法迂回的解决
  3. 实例不能有弱引用(weakref)目标,否则要记得把weakref放进slots

最后,namedlist和attrs提供了自动创建带slot的类,感兴趣的可以试试看。

Tuple

Python还有一个内置类型元组,用于表示不可变数据结构。 元组是固定的结构或记录,但没有字段名称。 对于字段访问,使用字段索引。 在创建元组实例时,元组字段一次性与值对象关联:

>>> ob = (1,2,3)
>>> x = ob[0]
>>> ob[1] = y # ERROR

元组的示例很简洁:

>>> print(sys.getsizeof(ob))
72

可以看只比slot多8byte:

字段 占用内存(bytes)
PyGC_Head 24
PyObject_HEAD 16
ob_size 8
[0] 8
[1] 8
[2] 8
TOTAL 72

Namedtuple

通过namedtuple我们也可以实现通过key值来访问tuple里的元素:

Point = namedtuple('Point', ('x', 'y', 'z'))

它创建了一个元组的子类,其中定义了用于按名称访问字段的描述符。 对于我们的例子,它看起来像这样:

class Point(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))

此类的所有实例都具有与元组相同的内存占用。 大量实例会留下稍大的内存占用:

数据量 内存占用
1 000 000 72 Mb
10 000 000 720 Mb
100 000 000 7.2 Gb

Recordclass

python的第三方库recordclassd提供了一个数据结构recordclass.mutabletuple,它几乎和内置tuple数据结构一致,但是占用更少的内存。

>>> Point = recordclass('Point', ('x', 'y', 'z'))
>>> ob = Point(1, 2, 3)

实例化以后,只少了PyGC_Head:

字段 占用内存
PyObject_HEAD 16
ob_size 8
x 8
y 8
y 8
TOTAL 48

到此,我们可以看到,和slot比,又进一步缩小了内存占用:

数据量 内存占用
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'))

另外一个方法是继承自dataobject

class Point(dataobject):
 x:int
 y:int
 z:int

以这种方式创建的类将创建不参与循环垃圾收集机制的实例。 内存中实例的结构与slots的情况相同,但没有PyGC_Head:

字段 内存占用(bytes)
PyObject_HEAD 16
x 8
y 8
y 8
TOTAL 40
>>> 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>})

数据量 内存占用
1 000 000 40 Mb
10 000 000 400 Mb
100 000 000 4.0 Gb

Cython

有一种方法基于Cython的使用。 它的优点是字段可以采用C语言原子类型的值。例如:

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 = Point(1,2,3)
>>> print(sys.getsizeof(ob))
32

内存结构分布如下:

字段 内存占用(bytes)
PyObject_HEAD 16
x 4
y 4
y 4
пусто 4
TOTAL 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)])

创建初始值是0的数组:

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

数据量 内存占用
1 000 000 12 Mb
10 000 000 120 Mb
100 000 000 1.2 Gb

最后

可以看出,在Python性能优化这方面,还是有很多事情可以做的。Python提供了方便的同时,也需要暂用较多的资源。在不通的场景下,我需要选择不同的处理方法,以便带来更好的性能体验。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Python 相关文章推荐
python使用socket进行简单网络连接的方法
Apr 29 Python
python笔记:mysql、redis操作方法
Jun 28 Python
Python实现按中文排序的方法示例
Apr 25 Python
对python的文件内注释 help注释方法
May 23 Python
pycharm访问mysql数据库的方法步骤
Jun 18 Python
在自动化中用python实现键盘操作的方法详解
Jul 19 Python
使用Django搭建web服务器的例子(最最正确的方式)
Aug 29 Python
python实现快递价格查询系统
Mar 03 Python
django自带的权限管理Permission用法说明
May 13 Python
Python API 操作Hadoop hdfs详解
Jun 06 Python
Python如何把字典写入到CSV文件的方法示例
Aug 23 Python
Python学习之os包使用教程详解
Mar 21 Python
解决Django加载静态资源失败的问题
Jul 28 #Python
django之静态文件 django 2.0 在网页中显示图片的例子
Jul 28 #Python
python正则-re的用法详解
Jul 28 #Python
django ModelForm修改显示缩略图 imagefield类型的实例
Jul 28 #Python
django之对FileField字段的upload_to的设定方法
Jul 28 #Python
Django ImageFiled上传照片并显示的方法
Jul 28 #Python
Python线上环境使用日志的及配置文件
Jul 28 #Python
You might like
php输出表格的实现代码(修正版)
2010/12/29 PHP
查找mysql字段中固定字符串并替换的几个方法
2012/09/23 PHP
ThinkPHP有变量的where条件分页实例
2014/11/03 PHP
PHP实现批量检测网站是否能够正常打开的方法
2016/08/23 PHP
Paypal实现循环扣款(订阅)功能
2017/03/23 PHP
js中的布尔运算符使用介绍
2013/11/20 Javascript
从零学JS之你需要了解的几本书
2014/05/19 Javascript
javascript结合canvas实现图片旋转效果
2015/05/03 Javascript
javascript实现行拖动的方法
2015/05/27 Javascript
jQuery操作属性和样式详解
2016/04/13 Javascript
快速使用Bootstrap搭建传送带
2016/05/06 Javascript
jQuery实现边框动态效果的实例代码
2016/09/23 Javascript
jQuery实现限制文本框的输入长度
2017/01/11 Javascript
javascript 判断一个对象为数组的方法
2017/05/03 Javascript
解决VUE中document.body.scrollTop为0的问题
2018/09/15 Javascript
Angular2实现的秒表及改良版示例
2019/05/10 Javascript
socket在egg中的使用实例代码详解
2019/05/30 Javascript
[02:02]特效爆炸!DOTA2珍宝之瓶待你开启
2018/08/21 DOTA
python实现无证书加密解密实例
2014/10/27 Python
rabbitmq(中间消息代理)在python中的使用详解
2017/12/14 Python
python利用sklearn包编写决策树源代码
2017/12/21 Python
浅析Python3爬虫登录模拟
2018/02/07 Python
Django如何配置mysql数据库
2018/05/04 Python
wxPython窗体拆分布局基础组件
2019/11/19 Python
python简单利用字典破解zip文件口令
2020/09/07 Python
俄语地区最大的中国商品在线购物网站之一:Umka Mall
2019/11/03 全球购物
销售人员中英文自荐信
2013/09/22 职场文书
前台接待的工作职责
2013/11/21 职场文书
劳动实践课感言
2014/02/01 职场文书
不忘国耻振兴中华演讲稿
2014/05/14 职场文书
酒店财务部岗位职责
2015/04/14 职场文书
幸福来敲门观后感
2015/06/04 职场文书
优秀党员主要事迹材料
2015/11/04 职场文书
人生感悟经典句子
2019/08/20 职场文书
Vue中使用import进行路由懒加载的原理分析
2022/04/01 Vue.js
iOS 16进一步确认,一共支持16款iPhone
2022/04/28 数码科技