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实现单词拼写检查
Apr 25 Python
pycharm远程调试openstack的图文教程
Nov 21 Python
安装python时MySQLdb报错的问题描述及解决方法
Mar 20 Python
python中for循环输出列表索引与对应的值方法
Nov 07 Python
uwsgi+nginx部署Django项目操作示例
Dec 04 Python
python3使用flask编写注册post接口的方法
Dec 28 Python
django 自定义过滤器的实现
Feb 26 Python
详解Numpy数组转置的三种方法T、transpose、swapaxes
May 27 Python
Pyinstaller 打包发布经验总结
Jun 02 Python
python 对一幅灰度图像进行直方图均衡化
Oct 27 Python
PyTorch梯度裁剪避免训练loss nan的操作
May 24 Python
浅析Python OpenCV三种滤镜效果
Apr 11 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
初识通用数据库操作类――前端easyui-datagrid,form(php)
2015/07/31 PHP
使用正则去除php代码中的注释方法
2016/11/03 PHP
php删除txt文件指定行及按行读取txt文档数据的方法
2017/01/30 PHP
PHP实现的简单在线计算器功能示例
2017/08/02 PHP
JavaScript语句可以不以;结尾的烦恼
2007/03/08 Javascript
js或css文件后面跟参数的原因说明
2010/01/09 Javascript
div浮层,滚动条移动,位置保持不变的4种方法汇总
2013/12/11 Javascript
Javascript中的默认参数详解
2014/10/22 Javascript
Jquery 实现弹出层插件
2015/01/28 Javascript
JavaScript中的标签语句用法分析
2015/02/10 Javascript
JavaScript中使用concat()方法拼接字符串的教程
2015/06/06 Javascript
JavaScript调用浏览器打印功能实例分析
2015/07/17 Javascript
快速获取/设置iframe内对象元素的几种js实现方法
2016/05/20 Javascript
js实现统计字符串中特定字符出现个数的方法
2016/08/02 Javascript
AngularJs Injecting Services Into Controllers详解
2016/09/02 Javascript
NodeJs搭建本地服务器之使用手机访问的实例讲解
2018/05/12 NodeJs
vue-cli项目中使用Mockjs详解
2018/05/14 Javascript
微信小程序 MinUI组件库系列之badge徽章组件示例
2018/08/20 Javascript
[55:35]VGJ.S vs Mski Supermajor小组赛C组 BO3 第二场 6.3
2018/06/04 DOTA
python文件特定行插入和替换实例详解
2017/07/12 Python
浅谈Python的list中的选取范围
2018/11/12 Python
django的ORM操作 删除和编辑实现详解
2019/07/24 Python
使用python自动追踪你的快递(物流推送邮箱)
2020/03/17 Python
python根据完整路径获得盘名/路径名/文件名/文件扩展名的方法
2020/04/22 Python
keras 模型参数,模型保存,中间结果输出操作
2020/07/06 Python
学习Python需要哪些工具
2020/09/04 Python
美国医疗用品、医疗设备和家庭保健用品商店:Medical Supply Depot
2018/07/08 全球购物
廉价连衣裙和婚纱礼服在线销售:Tbdress
2019/02/28 全球购物
Nanushka官网:匈牙利服装品牌
2019/08/14 全球购物
商场活动策划方案
2014/01/24 职场文书
学校交通安全责任书
2014/08/25 职场文书
2014年医生工作总结
2014/11/21 职场文书
个性与发展自我评价
2015/03/06 职场文书
英语导游欢迎词
2015/09/30 职场文书
20180830晚上第一届KSL半决赛 雨神vs解冻(二龙 三炮解说)
2022/04/01 星际争霸
【海涛DOTA解说】EVE女子战队独家录像加ZSMJ神牛两连发
2022/04/01 DOTA