前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入分析Python的内存机制

深入分析Python的内存机制

作者头像
哒呵呵
发布2019-07-17 20:22:37
1.1K0
发布2019-07-17 20:22:37
举报
文章被收录于专栏:鸿的学习笔记

编译自https://habr.com/en/post/458518/

当一个程序需要处理成千上万的object时,为object选择合适的数据结构减少内存的占用量就成了一个很重要的问题。 毕竟一台服务器的内存终究还是有限的。本文就是要简述在不同的数据结构下,一个单独的object的占用多大的空间,从而得出减少程序内存占用量的方案。

原文作者为了简化分析,选择实现一个三维向量[x, y, z]作为例子。

字典

字典是Python内置的数据结构,也是开发者最常用的数据数据结构。

代码语言:javascript
复制
>>> ob = {'x':1, 'y':2, 'z':3}
>>> x = ob['x']
>>> ob['y'] = y

使用sys.getsizeof(ob)获得此时ob对应的内存占用量。

代码语言:javascript
复制
>>> print(sys.getsizeof(ob))
240

绘制一张简单的表格比较下拥有大量实例的字典的占用空间。

Number of instances

Size of objects

1 000 000

240 Mb

10 000 000

2.40 Gb

100 000 000

24 Gb

类实例(Class instance)
代码语言:javascript
复制
class Point:
    #
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

>>> ob = Point(1,2,3)
>>> x = ob.x
>>> ob.y = y

类实例的内存占用很有趣:

Field

Size (bytes)

PyGC_Head

24

PyObject_HEAD

16

__weakref__

8

__dict__

8

TOTAL:

56

在这里__weakref__是与这个object有关的软引用的列表,__dict__是这个类实例的字典,包含了这个类实例的所有属性的值。

代码语言:javascript
复制
>>> print(sys.getsizeof(ob), sys.getsizeof(ob.__dict__)) 
56 112

它占用的空间比字典要少多了。

Number of instances

Size of objects

1 000 000

168 Mb

10 000 000

1.68 Gb

100 000 000

16.8 Gb

因为__dict__的存在,导致类实例依旧使用了大量的空间。

使用__slots__的类实例

在Python有个小技巧可以减少类实例存储属性的个数,那就是__slots__方法。

代码语言:javascript
复制
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

内存减少的就相当明显了。

Field

Size (bytes)

PyGC_Head

24

PyObject_HEAD

16

x

8

y

8

z

8

TOTAL:

64

因此在使用大量object时,内存占用量如下:

Number of instances

Size of objects

1 000 000

64 Mb

10 000 000

640 Mb

100 000 000

6.4 Gb

这里的内存占用量减少主要是因为类实例内部存储的属性数量减少了。

代码语言:javascript
复制
>>> pprint(Point.__dict__)
mappingproxy(
              ....................................
              'x': <member 'x' of 'Point' objects>,
              'y': <member 'y' of 'Point' objects>,
              'z': <member 'z' of 'Point' objects>})

这个颇类似于namedlist(https://pypi.org/project/namedlist/)。

代码语言:javascript
复制
>>> Point = namedlist('Point', ('x', 'y', 'z'))
元组

对于不可变的数据结构,Python中可以使用Tuple表示。只不过这时就不能通过指代x/y/z的名称获得相应的数据了。

代码语言:javascript
复制
>>> ob = (1,2,3)
>>> x = ob[0]
>>> ob[1] = y # ERROR

其占用的内存相对于使用__slots__的类实例还是多了 8 byte。

代码语言:javascript
复制
>>> print(sys.getsizeof(ob))
72

因为在Python里的list、tuple等数组类型都会拥有ob_size这个属性存储数组长度

Field

Size (bytes)

PyGC_Head

24

PyObject_HEAD

16

ob_size

8

[0]

8

[1]

8

[2]

8

TOTAL:

72

Namedtuple

Namedtuple弥补了tuple没有名称属性的缺点,也就是通过x/y/z调用对应的值。namedtuple在collections包内。

代码语言:javascript
复制
>>> Point = namedtuple('Point', ('x', 'y', 'z'))

其实现类似于下面的类

代码语言:javascript
复制
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))

因此它和tuple拥有一样的内存大小。

Number of instances

Size of objects

1 000 000

72 Mb

10 000 000

720 Mb

100 000 000

7.2 Gb

Recordclass

Recordclass是可变的Namedtuple,具体可以参考(https://stackoverflow.com/questions/29290359/existence-of-mutable-named-tuple-in-python)和(https://pypi.org/project/recordclass/)。Recordclass所有的API都和Namedtuple一样,但是Recordclass支持赋值和修改tuple。

代码语言:javascript
复制
 >>> Point = recordclass('Point', ('x', 'y', 'z'))
 >>> ob = Point(1, 2, 3)

与tuple相比,Recordclass没有了PyGC_Head这个字段,也就是意味着Recordclass没有参与Python的GC机制。GC需要开发者自己处理。

Field

Size (bytes)

PyObject_HEAD

16

ob_size

8

x

8

y

8

z

8

TOTAL:

48

这带来的内存节省也是明显的。

Number of instances

Size of objects

1 000 000

48 Mb

10 000 000

480 Mb

100 000 000

4.8 Gb

Dataobject

既然Recordclass是可变的Namedtuple,那么ob_size也无存在的必要。因此Recordclass基于__slot__机制,设计了dataclass方法。

代码语言:javascript
复制
>>> Point = make_dataclass('Point', ('x', 'y', 'z'))

或者通过继承recordclass.dataobject。

代码语言:javascript
复制
class Point(dataobject):
    x:int
    y:int
    z:int

同样的内存占用也变得极少。

Field

Size (bytes)

PyObject_HEAD

16

x

8

y

8

z

8

TOTAL:

40

代码语言:javascript
复制
>>> ob = Point(1,2,3)
>>> print(sys.getsizeof(ob))
40

没有了GC和ob_size,recordclass是通过直接访问内存地址获得x,y,z的值。

代码语言:javascript
复制
mappingproxy({'__new__': <staticmethod at 0x7f203c4e6be0>,
              .......................................
              'x': <recordclass.dataobject.dataslotgetset at 0x7f203c55c690>,
              'y': <recordclass.dataobject.dataslotgetset at 0x7f203c55c670>,
              'z': <recordclass.dataobject.dataslotgetset at 0x7f203c55c410>})

于是内存下降到一个很可观的程度了。

Number of instances

Size of objects

1 000 000

40 Mb

10 000 000

400 Mb

100 000 000

4.0 Gb

Cython

上面的方法依然没有脱离与Python的标准实现CPython,那么使用Cython(https://cython.org)实现类似的类实例。

代码语言:javascript
复制
cdef class Python:
    cdef public int x, y, z

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

因为是标准C的实现,那内存自然就少了。

代码语言:javascript
复制
>>> ob = Point(1,2,3)
>>> print(sys.getsizeof(ob))
32

其数据结构也发生改变了。

Field

Size (bytes)

PyObject_HEAD

16

x

4

y

4

z

4

пусто

4

TOTAL:

32

总的内存少了。

Number of instances

Size of objects

1 000 000

32 Mb

10 000 000

320 Mb

100 000 000

3.2 Gb

当然这还不是最有效的,接下来看看Numpy。

Numpy

不再使用Python灵活的动态特性,而是指定数据存储类型。

代码语言:javascript
复制
>>> Point = numpy.dtype(('x', numpy.int32), ('y', numpy.int32), ('z', numpy.int32)])

然后进行初始化。

代码语言:javascript
复制
 >>> points = numpy.zeros(N, dtype=Point)

其内存减少到了最低

Number of instances

Size of objects

1 000 000

12 Mb

10 000 000

120 Mb

100 000 000

1.2 Gb

但是一旦返回Python类型时,便要将int转换成Python Object,内存占用就变多了。

代码语言:javascript
复制
  >>> sys.getsizeof(points[0])
  68
结论

Python有时候会被称为胶水语言,就是因为与C良好的交互特性。当开发者对性能、内存占用等等有严苛的需求时,就向原文作者做的测试,Python可以使用C扩展极大的避免了Python本身的缺点。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-07-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 鸿的笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 字典
  • 类实例(Class instance)
  • 使用__slots__的类实例
  • 元组
  • Namedtuple
  • Recordclass
  • Dataobject
  • Cython
  • Numpy
  • 结论
相关产品与服务
数据保险箱
数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档