如何为创建大量实例节省内存?

需求:
某网络游戏中,定义了玩家类Player(id,name,status,...)每有一个在线玩家,在服务器程序内则有一个Player的实例,当在线人数很多时,将产生大量实例(如百万级)
如何降低这些大量实例的内存开销?

思路:
定义类的__slots__属性,它是用来声明实例属性名字的列表(关闭动态绑定)。

代码:

e.py:
class Player(object):
    def __init__(self,uid,name,status=0,level=1):
        self.uid = uid
        self.name = name
        self.stat = status
        self.level = level

class Player2(object):
    __slots__ = ['uid','name','stat','level'] # 声明类实例化对象所有的属性名,禁止实例属性的动态绑定。即该实例没有了__dict__属性。
    def __init__(self,uid,name,status=0,level=1):
        self.uid = uid
        self.name = name
        self.stat = status
        self.level = level

==============================================
测试:
In [3]: from e import Player, Player2

In [4]: p1 = Player('001','Jim')

In [5]: p2 = Player2('002','Tom')

In [6]: dir(p1)
Out[6]:
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'level',
 'name',
 'stat',
 'uid']

In [7]: dir(p2)
Out[7]:
['__class__',
 '__delattr__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__slots__',
 '__str__',
 '__subclasshook__',
 'level',
 'name',
 'stat',
 'uid']

In [8]: set(dir(p1)) - set(dir(p2))  # 取集合的差集,这里是p1比p2多的属性
Out[8]: {'__dict__', '__weakref__'}

In [9]: set(dir(p2)) - set(dir(p1))
Out[9]: {'__slots__'}

In [10]: p1.__dict__  # 这里__dict__是一个动态的字典
Out[10]: {'uid': '001', 'name': 'Jim', 'stat': 0, 'level': 1}

In [11]:  p1.x
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-11-54f8378113e3> in <module>
----> 1 p1.x

AttributeError: 'Player' object has no attribute 'x'

In [12]: p1.x = 123  # 可以动态的增加属性

In [13]: p1.x
Out[13]: 123

In [14]: p1.__dict__
Out[14]: {'uid': '001', 'name': 'Jim', 'stat': 0, 'level': 1, 'x': 123}

In [15]: p1.__dict__['y'] = 99

In [16]: p1.y
Out[16]: 99

In [17]: del p1.__dict__['x']  # 删除属性

In [18]: p1.x
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-18-54f8378113e3> in <module>
----> 1 p1.x

AttributeError: 'Player' object has no attribute 'x'

In [19]: import sys

In [20]: sys.getsizeof(p1.__dict__)  # 获取该属性所占用的内存空间,所以禁用该属性后占用的内存空间减少
Out[20]: 368

In [21]: p2.x = 123
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-21-2c5f7091e7e8> in <module>
----> 1 p2.x = 123

AttributeError: 'Player2' object has no attribute 'x'

In [22]: sys.getsizeof(p2.__slot__)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-22-d68996e840e6> in <module>
----> 1 sys.getsizeof(p2.__slot__)

AttributeError: 'Player2' object has no attribute '__slot__'

In [23]: sys.getsizeof(p2.__slots__)
Out[23]: 96

=====================================================================
>>> class Player1:
...     def __init__(self,uid,name,level):
...         self.uid = uid
...         self.name = name
...         self.level = level
... 
>>> class Player2:
...     __slots__=['uid','name','level']
...     def __init__(self,uid,name,level):
...         self.uid = uid
...         self.name = name
...         self.level = level
... 
>>> p1 = Player1('0001','Jim',20)
>>> p2 = Player2('0001','Jim',20)
>>> dir(p1)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'level', 'name', 'uid']
>>> dir(p2)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'level', 'name', 'uid']
>>> set(dir(p1)) - set(dir(p2))
{'__weakref__', '__dict__'}
>>> p1.x = 100
>>> p1.y = 200
>>> p1.x
100
>>> p1.xy
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Player1' object has no attribute 'xy'
>>> p1.y
200
>>> p1.__dict__
{'uid': '0001', 'name': 'Jim', 'level': 20, 'x': 100, 'y': 200}
>>> p1.__dict__['z'] = 300
>>> p1.z
300
>>> p1.__dict__.pop('x')
100
>>> p1.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Player1' object has no attribute 'x'
>>> import sys
>>> sys.getsizeof(p1.__dict__)
648
>>> sys.getsizeof(p1.name)
52
>>> sys.getsizeof(p1.level)
28
>>> sys.getsizeof(p1.uid)
53
>>> p2.x = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Player2' object has no attribute 'x'
>>> p2.name='abc'

=====================================================================================================

class Player1:
    def __init__(self,uid,name,level):
        self.uid = uid
        self.name = name
        self.level = level

class Player2:
    __slots__=['uid','name','level']
    def __init__(self,uid,name,level):
        self.uid = uid
        self.name = name
        self.level = level

import tracemalloc
tracemalloc.start()
# start
la = [Player1(1,2,3) for _ in range(100000)]
#lb = [Player2(1,2,3) for _ in range(100000)]
# end
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('filename')  # 统计整个文件占用内存的大小,通过对比可以发现lb占用的内在更小
for stat in top_stats[:10]:print(stat)

posted @ 2020-07-20 23:01  Richardo-M-Lu  阅读(108)  评论(0编辑  收藏  举报