PEP 412引入了对属性字典的改进处理,有效地减少了类实例的内存占用。 __slots__
的设计目的与此相同,因此使用__slots__
还有什么意义吗?
为了自己找出答案,我进行了以下测试,但结果没有多大意义:
class Slots(object):
__slots__ = ['a', 'b', 'c', 'd', 'e']
def __init__(self):
self.a = 1
self.b = 1
self.c = 1
self.d = 1
self.e = 1
class NoSlots(object):
def __init__(self):
self.a = 1
self.b = 1
self.c = 1
self.d = 1
self.e = 1
Python 3.3结果:
>>> sys.getsizeof([Slots() for i in range(1000)])
Out[1]: 9024
>>> sys.getsizeof([NoSlots() for i in range(1000)])
Out[1]: 9024
Python 2.7结果:
>>> sys.getsizeof([Slots() for i in range(1000)])
Out[1]: 4516
>>> sys.getsizeof([NoSlots() for i in range(1000)])
Out[1]: 4516
我原本希望大小至少与Python 2.7不同,所以我认为测试有问题。
答案 0 :(得分:5)
问题是sys.getsizeof()
,很少返回您的期望。例如,在这种情况下,它计算对象的“大小”而不考虑其__dict__
的大小。我建议您通过测量创建100'000个实例的实际内存使用情况来重试。
另请注意,Python 3.3的行为受PyPy启发,其中__slots__
没有任何区别,所以我希望它在Python 3.3中也没有区别。据我所知,__slots__
现在几乎没有任何用处。
答案 1 :(得分:4)
不,PEP 412 不使__slots__
多余。
首先,Armin Rigo是对的,你没有正确测量它。您需要衡量的是对象的大小,加上值,加上__dict__
本身(仅适用于NoSlots
)和键(仅适用于NoSlots
)。
或者你可以做他的建议:
cls = Slots if len(sys.argv) > 1 else NoSlots
def f():
tracemalloc.start()
objs = [cls() for _ in range(100000)]
print(tracemalloc.get_traced_memory())
f()
当我在OS X上的64位CPython 3.4上运行时,8824968
获得NoSlots
,25624872
获得Slots
。因此,看起来NoSlots
实例需要88个字节,而Slots
实例需要256个字节。
这怎么可能?
因为__slots__
和密钥分割__dict__
之间仍存在两个差异。
首先,字典使用的哈希表保持在2 / 3rds以下,并且它们以指数方式增长并且具有最小尺寸,因此您将拥有一些额外的空间。通过查看评论良好的source来计算多少空间并不难:您将拥有8个散列桶而不是5个插槽指针。
其次,字典本身并不是免费的;它有一个标准的对象标题,一个计数和两个指针。这听起来可能不是很多,但是当你谈论一个只有一些属性的对象时(注意大多数对象只有一些属性......), dict header可以与hash表一样多。
当然在你的例子中,值,所以这里涉及的唯一成本是对象本身,加上5个插槽或8个散列桶和dict标题,所以差别非常大。在现实生活中,__slots__
很少 。
最后,请注意PEP 412仅声称:
基准测试表明,面向对象程序的内存使用率降低了10%到20%
考虑一下您使用__slots__
的位置。要么储蓄如此巨大,以至于不使用__slots__
会是荒谬的,或者你真的需要挤出最后15%。或者你正在构建一个ABC或其他类,你希望通过谁知道什么来创建子类,子类可能需要节省。无论如何,在这些情况下,没有__slots__
,甚至三分之二的利益,你获得一半利益的事实仍然很少是足够的;您仍然需要使用__slots__
。
真正的胜利是在不值得使用__slots__
的情况下;你将免费获得一笔小额福利。
(另外,肯定有一些程序员过度使用__slots__
的地狱,也许这种改变可以说服他们中的一些人将精力放在微观上优化其他不太相关的东西,如果你&#39 ;很幸运。)