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 相关文章推荐
linux系统使用python监测系统负载脚本分享
Jan 15 Python
Python运算符重载用法实例分析
Jun 01 Python
深入解析Python中的descriptor描述器的作用及用法
Jun 27 Python
Python 登录网站详解及实例
Apr 11 Python
Python通过matplotlib画双层饼图及环形图简单示例
Dec 15 Python
Python数据分析之双色球中蓝红球分析统计示例
Feb 03 Python
使用python 3实现发送邮件功能
Jun 15 Python
JavaScript中的模拟事件和自定义事件实例分析
Jul 27 Python
如何利用Python模拟GitHub登录详解
Jul 15 Python
python线程定时器Timer实现原理解析
Nov 30 Python
基于Python pyecharts实现多种图例代码解析
Aug 10 Python
python爬取代理IP并进行有效的IP测试实现
Oct 09 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
dede全站URL静态化改造[070414更正]
2007/04/17 PHP
php自动加载的两种实现方法
2010/06/21 PHP
深入php 正则表达式的学习探讨
2013/06/06 PHP
php设置允许大文件上传示例代码
2014/03/10 PHP
php实现用于验证所有类型的信用卡类
2015/03/24 PHP
php中pcntl_fork创建子进程的方法实例
2019/03/14 PHP
JavaScript获取当前日期是星期几的方法
2015/04/06 Javascript
JavaScript中实现Map的示例代码
2015/09/09 Javascript
JavaScript DOM 学习总结(五)
2015/11/24 Javascript
基于jQuery实现文本框只能输入数字(小数、整数)
2016/01/14 Javascript
禁用backspace网页回退功能的实现代码
2016/11/15 Javascript
微信小程序 HTTPS报错整理常见问题及解决方案
2016/12/14 Javascript
JS验证字符串功能
2017/02/22 Javascript
Vue.js render方法使用详解
2017/04/05 Javascript
vue获取DOM元素并设置属性的两种实现方法
2017/09/30 Javascript
jQuery实现的事件绑定功能基本示例
2017/10/11 jQuery
Vue底层实现原理总结
2018/02/17 Javascript
微信小程序用户位置权限的获取方法(拒绝后提醒)
2018/11/15 Javascript
通过实例了解js函数中参数的传递
2019/06/15 Javascript
微信小程序如何播放腾讯视频的实现
2019/09/20 Javascript
vue+vant使用图片预览功能ImagePreview的问题解决
2020/04/10 Javascript
详解uniapp的全局变量实现方式
2021/01/11 Javascript
在Linux上安装Python的Flask框架和创建第一个app实例的教程
2015/03/30 Python
Python使用requests及BeautifulSoup构建爬虫实例代码
2018/01/24 Python
浅析python的Lambda表达式
2019/02/27 Python
python3中类的继承以及self和super的区别详解
2019/06/26 Python
详解Canvas事件绑定
2018/06/27 HTML / CSS
法国太阳镜店:Sunglasses Shop
2016/08/27 全球购物
在什么时候需要使用"常引用"
2015/12/31 面试题
学生请假条格式
2014/04/11 职场文书
公关活动策划方案
2014/05/25 职场文书
安全生产目标管理责任书
2014/07/25 职场文书
公司人事专员岗位职责
2014/08/11 职场文书
小升初自荐信范文
2015/03/05 职场文书
刑事法律意见书
2015/06/04 职场文书
保护动物的宣传语
2015/07/13 职场文书