class 的实例使用哪些资源?
What resources does an instance of a class use?
在为新创建的 class 实例分配资源时,python(我猜是 cpython)的效率如何?我有一种情况,我需要实例化一个节点 class 数百万次来构建树结构。每个节点对象应该是轻量级的,只包含一些数字和对父节点和子节点的引用。
例如,python 是否需要为每个实例化对象的所有 "double underscore" 属性分配内存(例如文档字符串、__dict__
、__repr__
、__class__
, etc, etc),要么单独创建这些属性,要么存储指向它们由 class?或者它是否高效并且不需要存储任何东西,除了我定义的需要存储在每个对象中的自定义内容?
[edit] 要准确测量 python 进程的内存使用情况并不容易; 我认为我的回答没有完全回答问题,但这是一种在某些情况下可能有用的方法。
大多数方法使用代理方法(创建 n 个对象并估计对系统内存的影响),以及试图包装这些方法的外部库。例如,可以找到线程 here, here, and there [/edit]
在 cPython 3.7
上,常规 class 实例的最小大小为 56 字节;使用 __slots__
(无字典),16 个字节。
import sys
class A:
pass
class B:
__slots__ = ()
pass
a = A()
b = B()
sys.getsizeof(a), sys.getsizeof(b)
输出:
56, 16
在实例级别找不到文档字符串、class 变量和类型注释:
import sys
class A:
"""regular class"""
a: int = 12
class B:
"""slotted class"""
b: int = 12
__slots__ = ()
a = A()
b = B()
sys.getsizeof(a), sys.getsizeof(b)
输出:
56, 16
[编辑]此外,参见 class 定义 的大小度量。 [/edit]
Is it efficient and does not need to store anything except the custom stuff I defined that needs to be stored in each object?
几乎是,除了某些 space。 Python 中的 Class 已经是 type
的实例,称为 metaclass。当新建一个 class 对象的实例时,custom stuff
就是 __init__
中的那些东西。 class中定义的属性和方法不会
花费更多 space.
至于某些space,参考一下Reblochon Masque的回答,很好,印象深刻
也许我可以举一个简单但说明性的例子:
class T(object):
def a(self):
print(self)
t = T()
t.a()
# output: <__main__.T object at 0x1060712e8>
T.a(t)
# output: <__main__.T object at 0x1060712e8>
# as you see, t.a() equals T.a(t)
import sys
sys.getsizeof(T)
# output: 1056
sys.getsizeof(T())
# output: 56
从表面上看很简单:方法、class 变量和 class 文档字符串存储在 class 中(函数文档字符串存储在函数中)。实例变量存储在实例中。该实例还引用了 class,因此您可以查找这些方法。通常所有这些都存储在字典中(__dict__
)。
所以是的,简短的回答是:Python 不在实例中存储方法,但所有实例都需要引用 class.
例如,如果您有一个像这样的简单 class:
class MyClass:
def __init__(self):
self.a = 1
self.b = 2
def __repr__(self):
return f"{self.__class__.__name__}({self.a}, {self.b})"
instance_1 = MyClass()
instance_2 = MyClass()
然后 in-memory 它看起来(非常简单)如下所示:
更深入
然而,在深入学习 CPython 时,有几件事很重要:
- 将字典作为抽象会导致相当多的开销:您需要对实例字典(字节)的引用,并且字典中的每个条目都存储哈希(8 字节)、指向键的指针(8 字节)和一个指向存储属性的指针(另外 8 个字节)。字典通常也是 over-allocate 这样添加另一个属性不会触发 dictionary-resize.
- Python没有"value-types",即使是整数也是一个实例。这意味着您不需要 4 个字节来存储整数 - Python 需要(在我的计算机上)24 个字节来存储整数 0 和至少 28 个字节来存储不为零的整数。然而,对其他对象的引用只需要 8 个字节(指针)。
- CPython 使用引用计数,因此每个实例都需要一个引用计数(8 字节)。此外,大多数 CPythons classes 都参与循环垃圾收集器,这会导致每个实例另外 24 字节的开销。除了这些可以是weak-referenced的classes(大部分)还有一个
__weakref__
字段(另外8个字节)。
此时也有必要指出 CPython 优化了其中的一些 "problems":
- Python 使用 Key-Sharing Dictionaries 来避免实例字典的一些内存开销(散列和键)。
- 您可以在 class 中使用
__slots__
来避免 __dict__
和 __weakref__
。这可以显着减少每个实例的 memory-footprint。
- Python 实习一些值,例如,如果您创建一个小整数,它不会创建一个新的整数实例,而是 return 对已存在实例的引用。
考虑到所有这些以及其中的几个要点(尤其是关于优化的要点)是 implementation-details 很难给出关于 Python 的有效 memory-requirements classes.
减少实例的内存占用
但是,如果您想减少实例的 memory-footprint,一定要尝试 __slots__
。它们确实有 draw-backs,但如果它们不适用于您,它们是减少内存的好方法。
class Slotted:
__slots__ = ('a', 'b')
def __init__(self):
self.a = 1
self.b = 1
如果这还不够,并且您操作了很多 "value types",您还可以更进一步,创建扩展 classes。这些是在 C 中定义的 classes,但被包装以便您可以在 Python.
中使用它们
为方便起见,我在这里使用 Cython 的 IPython 绑定来模拟扩展 class:
%load_ext cython
%%cython
cdef class Extensioned:
cdef long long a
cdef long long b
def __init__(self):
self.a = 1
self.b = 1
正在测量内存使用情况
在所有这些理论之后剩下的有趣问题是:我们如何测量记忆?
我也用普通的class:
class Dicted:
def __init__(self):
self.a = 1
self.b = 1
我通常使用 psutil
(即使它是一种代理方法)来测量内存影响,并简单地测量前后使用了多少内存。测量值有点偏移,因为我需要以某种方式将实例保留在内存中,否则内存将被回收(立即)。此外,这只是一个近似值,因为 Python 实际上做了相当多的内存管理,尤其是当有很多 create/deletes.
时
import os
import psutil
process = psutil.Process(os.getpid())
runs = 10
instances = 100_000
memory_dicted = [0] * runs
memory_slotted = [0] * runs
memory_extensioned = [0] * runs
for run_index in range(runs):
for store, cls in [(memory_dicted, Dicted), (memory_slotted, Slotted), (memory_extensioned, Extensioned)]:
before = process.memory_info().rss
l = [cls() for _ in range(instances)]
store[run_index] = process.memory_info().rss - before
l.clear() # reclaim memory for instances immediately
每个 运行 的内存不会完全相同,因为 Python re-uses 一些内存,有时还保留内存用于其他目的,但它至少应该给出一个合理的提示:
>>> min(memory_dicted) / 1024**2, min(memory_slotted) / 1024**2, min(memory_extensioned) / 1024**2
(15.625, 5.3359375, 2.7265625)
我在这里使用 min
主要是因为我对最小值感兴趣,我除以 1024**2
将字节转换为兆字节。
总结:正如预期的那样,带 dict 的普通 class 比带插槽的 classes 需要更多的内存,但扩展 classes(如果适用且可用)可以有更低的内存内存占用。
另一个可以非常方便地测量内存使用情况的工具是 memory_profiler
,尽管我已经有一段时间没有使用它了。
CPython中最基本的对象就是一个type reference and reference count。两者都是 word-sized(即在 64 位机器上为 8 字节),因此实例的最小大小为 2 个字(即在 64 位机器上为 16 字节)。
>>> import sys
>>>
>>> class Minimal:
... __slots__ = () # do not allow dynamic fields
...
>>> minimal = Minimal()
>>> sys.getsizeof(minimal)
16
每个实例需要 space 用于 __class__
和一个隐藏的引用计数。
类型引用(大约 object.__class__
)意味着 实例从它们的 class 中获取内容。您在 class 上定义的所有内容,而不是实例,不会占用每个实例 space。
>>> class EmptyInstance:
... __slots__ = () # do not allow dynamic fields
... foo = 'bar'
... def hello(self):
... return "Hello World"
...
>>> empty_instance = EmptyInstance()
>>> sys.getsizeof(empty_instance) # instance size is unchanged
16
>>> empty_instance.foo # instance has access to class attributes
'bar'
>>> empty_instance.hello() # methods are class attributes!
'Hello World'
请注意,方法也是 class 上的函数。通过实例获取一个调用 function's data descriptor protocol 通过将实例部分绑定到函数来创建临时方法对象。因此,方法不会增加实例大小。
实例不需要 space class 属性,包括 __doc__
和 any 方法。
唯一增加实例大小的是存储在实例上的内容。可以通过三种方式实现:__dict__
、__slots__
和 container types。所有这些都以某种方式存储分配给实例的内容。
默认情况下,实例有一个 __dict__
field - 对存储属性的映射的引用。这样的 classes also 还有一些其他的默认字段,比如 __weakref__
.
>>> class Dict:
... # class scope
... def __init__(self):
... # instance scope - access via self
... self.bar = 2 # assign to instance
...
>>> dict_instance = Dict()
>>> dict_instance.foo = 1 # assign to instance
>>> sys.getsizeof(dict_instance) # larger due to more references
56
>>> sys.getsizeof(dict_instance.__dict__) # __dict__ takes up space as well!
240
>>> dict_instance.__dict__ # __dict__ stores attribute names and values
{'bar': 2, 'foo': 1}
每个使用 __dict__
的实例使用 space 作为 dict
,属性名称和值。
添加 __slots__
field to the class 生成具有固定数据布局的实例。这将允许的属性限制为声明的属性,但在实例上占用很少 space 。 __dict__
和 __weakref__
插槽仅根据要求创建。
>>> class Slots:
... __slots__ = ('foo',) # request accessors for instance data
... def __init__(self):
... # instance scope - access via self
... self.foo = 2
...
>>> slots_instance = Slots()
>>> sys.getsizeof(slots_instance) # 40 + 8 * fields
48
>>> slots_instance.bar = 1
AttributeError: 'Slots' object has no attribute 'bar'
>>> del slots_instance.foo
>>> sys.getsizeof(slots_instance) # size is fixed
48
>>> Slots.foo # attribute interface is descriptor on class
<member 'foo' of 'Slots' objects>
每个使用 __slots__
的实例仅将 space 用于属性值。
从容器类型继承,例如 list
、dict
或 tuple
,允许存储项 (self[0]
) 而不是属性(self.a
)。除了 __dict__
或 __slots__
之外,这还使用了紧凑的内部存储 。这样的 classes 很少被手动构造 - 经常使用诸如 typing.NamedTuple
的助手。
>>> from typing import NamedTuple
>>>
>>> class Named(NamedTuple):
... foo: int
...
>>> named_instance = Named(2)
>>> sys.getsizeof(named_instance)
56
>>> named_instance.bar = 1
AttributeError: 'Named' object has no attribute 'bar'
>>> del named_instance.foo # behaviour inherited from container
AttributeError: can't delete attribute
>>> Named.foo # attribute interface is descriptor on class
<property at 0x10bba3228>
>>> Named.__len__ # container interface/metadata such as length exists
<slot wrapper '__len__' of 'tuple' objects>
派生容器的每个实例的行为都像基本类型,加上潜在的 __slots__
或 __dict__
.
最轻量级实例使用__slots__
仅存储属性值。
请注意,__dict__
开销的一部分通常由 Python 解释器优化。 CPython 能够在 __dict__
和 __slots__
之间 sharing keys between instances, which can . PyPy uses an optimises key-shared representation that completely eliminates the difference。
除了最微不足道的情况外,不可能准确测量对象的内存消耗。测量孤立对象的大小会遗漏相关结构,例如 __dict__
使用内存 both 实例上的指针 和 外部 dict
。测量对象组会错误地计算共享对象(驻留字符串、小整数...)和惰性对象(例如 __dict__
的 dict
仅在访问时存在)。请注意 PyPy does not implement sys.getsizeof
.
为了测量内存消耗,应该使用完整的程序测量。例如,可以使用 resource
or psutils
to get the own memory consumption while spawning objects.
我创造了一个这样的 measurement script for number of fields, number of instances and implementation variant。对于 1000000 的实例计数,在 CPython 3.7.0 和 PyPy3 3.6.1/7.1.1-beta0.[=62 上显示的值为 bytes/field =]
# fields | 1 | 4 | 8 | 16 | 32 | 64 |
---------------+-------+-------+-------+-------+-------+-------+
python3: slots | 48.8 | 18.3 | 13.5 | 10.7 | 9.8 | 8.8 |
python3: dict | 170.6 | 42.7 | 26.5 | 18.8 | 14.7 | 13.0 |
pypy3: slots | 79.0 | 31.8 | 30.1 | 25.9 | 25.6 | 24.1 |
pypy3: dict | 79.2 | 31.9 | 29.9 | 27.2 | 24.9 | 25.0 |
对于 CPython,__slots__
比 __dict__
节省大约 30%-50% 的内存。对于 PyPy,消耗是可比的。有趣的是,PyPy 比 CPython 差 __slots__
,并且在极端字段计数下保持稳定。
在为新创建的 class 实例分配资源时,python(我猜是 cpython)的效率如何?我有一种情况,我需要实例化一个节点 class 数百万次来构建树结构。每个节点对象应该是轻量级的,只包含一些数字和对父节点和子节点的引用。
例如,python 是否需要为每个实例化对象的所有 "double underscore" 属性分配内存(例如文档字符串、__dict__
、__repr__
、__class__
, etc, etc),要么单独创建这些属性,要么存储指向它们由 class?或者它是否高效并且不需要存储任何东西,除了我定义的需要存储在每个对象中的自定义内容?
[edit] 要准确测量 python 进程的内存使用情况并不容易; 我认为我的回答没有完全回答问题,但这是一种在某些情况下可能有用的方法。
大多数方法使用代理方法(创建 n 个对象并估计对系统内存的影响),以及试图包装这些方法的外部库。例如,可以找到线程 here, here, and there [/edit]
在 cPython 3.7
上,常规 class 实例的最小大小为 56 字节;使用 __slots__
(无字典),16 个字节。
import sys
class A:
pass
class B:
__slots__ = ()
pass
a = A()
b = B()
sys.getsizeof(a), sys.getsizeof(b)
输出:
56, 16
在实例级别找不到文档字符串、class 变量和类型注释:
import sys
class A:
"""regular class"""
a: int = 12
class B:
"""slotted class"""
b: int = 12
__slots__ = ()
a = A()
b = B()
sys.getsizeof(a), sys.getsizeof(b)
输出:
56, 16
[编辑]此外,参见
Is it efficient and does not need to store anything except the custom stuff I defined that needs to be stored in each object?
几乎是,除了某些 space。 Python 中的 Class 已经是 type
的实例,称为 metaclass。当新建一个 class 对象的实例时,custom stuff
就是 __init__
中的那些东西。 class中定义的属性和方法不会
花费更多 space.
至于某些space,参考一下Reblochon Masque的回答,很好,印象深刻
也许我可以举一个简单但说明性的例子:
class T(object):
def a(self):
print(self)
t = T()
t.a()
# output: <__main__.T object at 0x1060712e8>
T.a(t)
# output: <__main__.T object at 0x1060712e8>
# as you see, t.a() equals T.a(t)
import sys
sys.getsizeof(T)
# output: 1056
sys.getsizeof(T())
# output: 56
从表面上看很简单:方法、class 变量和 class 文档字符串存储在 class 中(函数文档字符串存储在函数中)。实例变量存储在实例中。该实例还引用了 class,因此您可以查找这些方法。通常所有这些都存储在字典中(__dict__
)。
所以是的,简短的回答是:Python 不在实例中存储方法,但所有实例都需要引用 class.
例如,如果您有一个像这样的简单 class:
class MyClass:
def __init__(self):
self.a = 1
self.b = 2
def __repr__(self):
return f"{self.__class__.__name__}({self.a}, {self.b})"
instance_1 = MyClass()
instance_2 = MyClass()
然后 in-memory 它看起来(非常简单)如下所示:
更深入
然而,在深入学习 CPython 时,有几件事很重要:
- 将字典作为抽象会导致相当多的开销:您需要对实例字典(字节)的引用,并且字典中的每个条目都存储哈希(8 字节)、指向键的指针(8 字节)和一个指向存储属性的指针(另外 8 个字节)。字典通常也是 over-allocate 这样添加另一个属性不会触发 dictionary-resize.
- Python没有"value-types",即使是整数也是一个实例。这意味着您不需要 4 个字节来存储整数 - Python 需要(在我的计算机上)24 个字节来存储整数 0 和至少 28 个字节来存储不为零的整数。然而,对其他对象的引用只需要 8 个字节(指针)。
- CPython 使用引用计数,因此每个实例都需要一个引用计数(8 字节)。此外,大多数 CPythons classes 都参与循环垃圾收集器,这会导致每个实例另外 24 字节的开销。除了这些可以是weak-referenced的classes(大部分)还有一个
__weakref__
字段(另外8个字节)。
此时也有必要指出 CPython 优化了其中的一些 "problems":
- Python 使用 Key-Sharing Dictionaries 来避免实例字典的一些内存开销(散列和键)。
- 您可以在 class 中使用
__slots__
来避免__dict__
和__weakref__
。这可以显着减少每个实例的 memory-footprint。 - Python 实习一些值,例如,如果您创建一个小整数,它不会创建一个新的整数实例,而是 return 对已存在实例的引用。
考虑到所有这些以及其中的几个要点(尤其是关于优化的要点)是 implementation-details 很难给出关于 Python 的有效 memory-requirements classes.
减少实例的内存占用
但是,如果您想减少实例的 memory-footprint,一定要尝试 __slots__
。它们确实有 draw-backs,但如果它们不适用于您,它们是减少内存的好方法。
class Slotted:
__slots__ = ('a', 'b')
def __init__(self):
self.a = 1
self.b = 1
如果这还不够,并且您操作了很多 "value types",您还可以更进一步,创建扩展 classes。这些是在 C 中定义的 classes,但被包装以便您可以在 Python.
中使用它们为方便起见,我在这里使用 Cython 的 IPython 绑定来模拟扩展 class:
%load_ext cython
%%cython
cdef class Extensioned:
cdef long long a
cdef long long b
def __init__(self):
self.a = 1
self.b = 1
正在测量内存使用情况
在所有这些理论之后剩下的有趣问题是:我们如何测量记忆?
我也用普通的class:
class Dicted:
def __init__(self):
self.a = 1
self.b = 1
我通常使用 psutil
(即使它是一种代理方法)来测量内存影响,并简单地测量前后使用了多少内存。测量值有点偏移,因为我需要以某种方式将实例保留在内存中,否则内存将被回收(立即)。此外,这只是一个近似值,因为 Python 实际上做了相当多的内存管理,尤其是当有很多 create/deletes.
import os
import psutil
process = psutil.Process(os.getpid())
runs = 10
instances = 100_000
memory_dicted = [0] * runs
memory_slotted = [0] * runs
memory_extensioned = [0] * runs
for run_index in range(runs):
for store, cls in [(memory_dicted, Dicted), (memory_slotted, Slotted), (memory_extensioned, Extensioned)]:
before = process.memory_info().rss
l = [cls() for _ in range(instances)]
store[run_index] = process.memory_info().rss - before
l.clear() # reclaim memory for instances immediately
每个 运行 的内存不会完全相同,因为 Python re-uses 一些内存,有时还保留内存用于其他目的,但它至少应该给出一个合理的提示:
>>> min(memory_dicted) / 1024**2, min(memory_slotted) / 1024**2, min(memory_extensioned) / 1024**2
(15.625, 5.3359375, 2.7265625)
我在这里使用 min
主要是因为我对最小值感兴趣,我除以 1024**2
将字节转换为兆字节。
总结:正如预期的那样,带 dict 的普通 class 比带插槽的 classes 需要更多的内存,但扩展 classes(如果适用且可用)可以有更低的内存内存占用。
另一个可以非常方便地测量内存使用情况的工具是 memory_profiler
,尽管我已经有一段时间没有使用它了。
CPython中最基本的对象就是一个type reference and reference count。两者都是 word-sized(即在 64 位机器上为 8 字节),因此实例的最小大小为 2 个字(即在 64 位机器上为 16 字节)。
>>> import sys
>>>
>>> class Minimal:
... __slots__ = () # do not allow dynamic fields
...
>>> minimal = Minimal()
>>> sys.getsizeof(minimal)
16
每个实例需要 space 用于 __class__
和一个隐藏的引用计数。
类型引用(大约 object.__class__
)意味着 实例从它们的 class 中获取内容。您在 class 上定义的所有内容,而不是实例,不会占用每个实例 space。
>>> class EmptyInstance:
... __slots__ = () # do not allow dynamic fields
... foo = 'bar'
... def hello(self):
... return "Hello World"
...
>>> empty_instance = EmptyInstance()
>>> sys.getsizeof(empty_instance) # instance size is unchanged
16
>>> empty_instance.foo # instance has access to class attributes
'bar'
>>> empty_instance.hello() # methods are class attributes!
'Hello World'
请注意,方法也是 class 上的函数。通过实例获取一个调用 function's data descriptor protocol 通过将实例部分绑定到函数来创建临时方法对象。因此,方法不会增加实例大小。
实例不需要 space class 属性,包括 __doc__
和 any 方法。
唯一增加实例大小的是存储在实例上的内容。可以通过三种方式实现:__dict__
、__slots__
和 container types。所有这些都以某种方式存储分配给实例的内容。
默认情况下,实例有一个
__dict__
field - 对存储属性的映射的引用。这样的 classes also 还有一些其他的默认字段,比如__weakref__
.>>> class Dict: ... # class scope ... def __init__(self): ... # instance scope - access via self ... self.bar = 2 # assign to instance ... >>> dict_instance = Dict() >>> dict_instance.foo = 1 # assign to instance >>> sys.getsizeof(dict_instance) # larger due to more references 56 >>> sys.getsizeof(dict_instance.__dict__) # __dict__ takes up space as well! 240 >>> dict_instance.__dict__ # __dict__ stores attribute names and values {'bar': 2, 'foo': 1}
每个使用
__dict__
的实例使用 space 作为dict
,属性名称和值。添加
__slots__
field to the class 生成具有固定数据布局的实例。这将允许的属性限制为声明的属性,但在实例上占用很少 space 。__dict__
和__weakref__
插槽仅根据要求创建。>>> class Slots: ... __slots__ = ('foo',) # request accessors for instance data ... def __init__(self): ... # instance scope - access via self ... self.foo = 2 ... >>> slots_instance = Slots() >>> sys.getsizeof(slots_instance) # 40 + 8 * fields 48 >>> slots_instance.bar = 1 AttributeError: 'Slots' object has no attribute 'bar' >>> del slots_instance.foo >>> sys.getsizeof(slots_instance) # size is fixed 48 >>> Slots.foo # attribute interface is descriptor on class <member 'foo' of 'Slots' objects>
每个使用
__slots__
的实例仅将 space 用于属性值。从容器类型继承,例如
list
、dict
或tuple
,允许存储项 (self[0]
) 而不是属性(self.a
)。除了__dict__
或__slots__
之外,这还使用了紧凑的内部存储 。这样的 classes 很少被手动构造 - 经常使用诸如typing.NamedTuple
的助手。>>> from typing import NamedTuple >>> >>> class Named(NamedTuple): ... foo: int ... >>> named_instance = Named(2) >>> sys.getsizeof(named_instance) 56 >>> named_instance.bar = 1 AttributeError: 'Named' object has no attribute 'bar' >>> del named_instance.foo # behaviour inherited from container AttributeError: can't delete attribute >>> Named.foo # attribute interface is descriptor on class <property at 0x10bba3228> >>> Named.__len__ # container interface/metadata such as length exists <slot wrapper '__len__' of 'tuple' objects>
派生容器的每个实例的行为都像基本类型,加上潜在的
__slots__
或__dict__
.
最轻量级实例使用__slots__
仅存储属性值。
请注意,__dict__
开销的一部分通常由 Python 解释器优化。 CPython 能够在 __dict__
和 __slots__
之间 sharing keys between instances, which can
除了最微不足道的情况外,不可能准确测量对象的内存消耗。测量孤立对象的大小会遗漏相关结构,例如 __dict__
使用内存 both 实例上的指针 和 外部 dict
。测量对象组会错误地计算共享对象(驻留字符串、小整数...)和惰性对象(例如 __dict__
的 dict
仅在访问时存在)。请注意 PyPy does not implement sys.getsizeof
为了测量内存消耗,应该使用完整的程序测量。例如,可以使用 resource
or psutils
to get the own memory consumption while spawning objects.
我创造了一个这样的 measurement script for number of fields, number of instances and implementation variant。对于 1000000 的实例计数,在 CPython 3.7.0 和 PyPy3 3.6.1/7.1.1-beta0.[=62 上显示的值为 bytes/field =]
# fields | 1 | 4 | 8 | 16 | 32 | 64 |
---------------+-------+-------+-------+-------+-------+-------+
python3: slots | 48.8 | 18.3 | 13.5 | 10.7 | 9.8 | 8.8 |
python3: dict | 170.6 | 42.7 | 26.5 | 18.8 | 14.7 | 13.0 |
pypy3: slots | 79.0 | 31.8 | 30.1 | 25.9 | 25.6 | 24.1 |
pypy3: dict | 79.2 | 31.9 | 29.9 | 27.2 | 24.9 | 25.0 |
对于 CPython,__slots__
比 __dict__
节省大约 30%-50% 的内存。对于 PyPy,消耗是可比的。有趣的是,PyPy 比 CPython 差 __slots__
,并且在极端字段计数下保持稳定。