在 Python 中准确测量对象大小 - Sys.GetSizeOf 不起作用
Posted
技术标签:
【中文标题】在 Python 中准确测量对象大小 - Sys.GetSizeOf 不起作用【英文标题】:Measure Object Size Accurately in Python - Sys.GetSizeOf not functioning 【发布时间】:2012-07-03 07:36:52 【问题描述】:我试图准确/明确地找到 Python 中两个不同类之间的大小差异。它们都是新的样式类,除了一个没有定义slots。我已经尝试了许多测试来确定它们的大小差异,但它们最终在内存使用方面总是相同的。
到目前为止,我已经尝试了 sys.GetSizeOf(obj) 和 heapy 的 heap() 函数,没有任何积极的结果。测试代码如下:
import sys
from guppy import hpy
class test3(object):
def __init__(self):
self.one = 1
self.two = "two variable"
class test4(object):
__slots__ = ('one', 'two')
def __init__(self):
self.one = 1
self.two = "two variable"
test3_obj = test3()
print "Sizeof test3_obj", sys.getsizeof(test3_obj)
test4_obj = test4()
print "Sizeof test4_obj", sys.getsizeof(test4_obj)
arr_test3 = []
arr_test4 = []
for i in range(3000):
arr_test3.append(test3())
arr_test4.append(test4())
h = hpy()
print h.heap()
输出:
Sizeof test3_obj 32 Sizeof test4_obj 32 Partition of a set of 34717 objects. Total size = 2589028 bytes. Index Count % Size % Cumulative % Kind (class / dict of class) 0 11896 34 765040 30 765040 30 str 1 3001 9 420140 16 1185180 46 dict of __main__.test3 2 5573 16 225240 9 1410420 54 tuple 3 348 1 167376 6 1577796 61 dict (no owner) 4 1567 5 106556 4 1684352 65 types.CodeType 5 68 0 105136 4 1789488 69 dict of module 6 183 1 97428 4 1886916 73 dict of type 7 3001 9 96032 4 1982948 77 __main__.test3 8 3001 9 96032 4 2078980 80 __main__.test4 9 203 1 90360 3 2169340 84 type <99 more rows. Type e.g. '_.more' to view.>
这就是 Python 2.6.0 的全部内容。我还尝试覆盖类的 sizeof 方法,尝试通过对各个 sizeofs 求和来确定大小,但这并没有产生任何不同的结果:
class test4(object):
__slots__ = ('one', 'two')
def __init__(self):
self.one = 1
self.two = "two variable"
def __sizeof__(self):
return super(test4, self).__sizeof__() + self.one.__sizeof__() + self.two.__sizeof__()
覆盖 sizeof 方法的结果:
Sizeof test3_obj 80 Sizeof test4_obj 80
【问题讨论】:
【参考方案1】:sys.getsizeof
返回一个比人们想象的更专业、更没用的数字。事实上,如果你将属性数增加到六个,你的 test3_obj 仍然是 32,但 test4_obj 会跳转到 48 个字节。这是因为 getsizeof 正在返回实现类型的 PyObject 结构的大小,对于 test3_obj 不包括保存属性的字典,但对于 test4_obj,属性不存储在字典中,它们存储在插槽中,所以它们被计入大小。
但是用__slots__
定义的类比没有用__slots__
定义的类占用更少的内存,正是因为没有字典来保存属性。
为什么要覆盖__sizeof__
?你真正想要完成什么?
【讨论】:
sizeof 覆盖是为了查看是否内置的 sizeof 方法没有正确测量变量的大小。 那么您认为确定此类简单对象之间大小差异的最佳方法是什么? 这取决于你为什么想知道大小。你想解决什么问题? 我想知道大小,这样我就可以明确地决定选择哪种数据结构。如果是这样,那么定量大小差异是多少,以便可以判断该开关是否证明了内存收益的合理性。 您应该编写程序,使用抽象来隐藏选择,然后测量程序的实际内存占用量。这是知道你真正问题的答案的唯一方法。__slots__
旨在减少对象的内存占用,尤其是在您有许多小对象的情况下。【参考方案2】:
正如其他人所说,sys.getsizeof
仅返回代表您的数据的对象结构的大小。因此,例如,如果您有一个不断向其中添加元素的动态数组,sys.getsizeof(my_array)
只会显示基本 DynamicArray
对象的大小,而不是其元素占用的不断增长的内存大小。
pympler.asizeof.asizeof()
给出了对象的大致完整尺寸,对您来说可能更准确。
from pympler import asizeof
asizeof.asizeof(my_object) # should give you the full object size
【讨论】:
【参考方案3】:首先在你的操作系统内存管理器中检查 Pyton 进程的大小,不要有很多对象。
第二次制作一个种类的多个对象,然后再次检查大小。
第三次制作许多其他类型的对象并检查大小。
重复几次,如果每个步骤的大小保持大致相同,您就得到了可比较的结果。
【讨论】:
我很好奇这会给我带来什么样的准确性?另外...我需要一种有效的方法来多次运行它,然后将其全部平均。【参考方案4】:以下功能已在 Python 3.6,64 位系统中测试。 它对我非常有用。 (我是从网上找来的,然后根据我的风格进行了调整, 并添加了“slots”功能。 我无法再次找到原始来源。)
def getSize(obj, seen: Optional[Set[int]] = None) -> int:
"""Recursively finds size of objects. Needs: import sys """
seen = set() if seen is None else seen
if id(obj) in seen: return 0 # to handle self-referential objects
seen.add(id(obj))
size = sys.getsizeof(obj, 0) # pypy3 always returns default (necessary)
if isinstance(obj, dict):
size += sum(getSize(v, seen) + getSize(k, seen) for k, v in obj.items())
elif hasattr(obj, '__dict__'):
size += getSize(obj.__dict__, seen)
elif hasattr(obj, '__slots__'): # in case slots are in use
slotList = [getattr(C, "__slots__", []) for C in obj.__class__.__mro__]
slotList = [[slot] if isinstance(slot, str) else slot for slot in slotList]
size += sum(getSize(getattr(obj, a, None), seen) for slot in slotList for a in slot)
elif hasattr(obj, '__iter__') and not isinstance(obj, (str, bytes, bytearray)):
size += sum(getSize(i, seen) for i in obj)
return size
现在对于以下类的对象,
class test3(object):
def __init__(self):
self.one = 1
self.two = "two variable"
class test4(object):
__slots__ = ('one', 'two')
def __init__(self):
self.one = 1
self.two = "two variable"
得到以下结果,
In [21]: t3 = test3()
In [22]: getSize(t3)
Out[22]: 361
In [23]: t4 = test4()
In [25]: getSize(t4)
Out[25]: 145
欢迎反馈改进功能。
【讨论】:
【参考方案5】:我遇到了类似的问题,最终编写了自己的助手来完成这项肮脏的工作。看看here
【讨论】:
【参考方案6】:您可能希望使用不同的实现来获取内存中对象的大小:
>>> import sys, array
>>> sizeof = lambda obj: sum(map(sys.getsizeof, explore(obj, set())))
>>> def explore(obj, memo):
loc = id(obj)
if loc not in memo:
memo.add(loc)
yield obj
if isinstance(obj, memoryview):
yield from explore(obj.obj, memo)
elif not isinstance(obj, (range, str, bytes, bytearray, array.array)):
# Handle instances with slots.
try:
slots = obj.__slots__
except AttributeError:
pass
else:
for name in slots:
try:
attr = getattr(obj, name)
except AttributeError:
pass
else:
yield from explore(attr, memo)
# Handle instances with dict.
try:
attrs = obj.__dict__
except AttributeError:
pass
else:
yield from explore(attrs, memo)
# Handle dicts or iterables.
for name in 'keys', 'values', '__iter__':
try:
attr = getattr(obj, name)
except AttributeError:
pass
else:
for item in attr():
yield from explore(item, memo)
>>> class Test1:
def __init__(self):
self.one = 1
self.two = 'two variable'
>>> class Test2:
__slots__ = 'one', 'two'
def __init__(self):
self.one = 1
self.two = 'two variable'
>>> print('sizeof(Test1()) ==', sizeof(Test1()))
sizeof(Test1()) == 361
>>> print('sizeof(Test2()) ==', sizeof(Test2()))
sizeof(Test2()) == 145
>>> array_test1, array_test2 = [], []
>>> for _ in range(3000):
array_test1.append(Test1())
array_test2.append(Test2())
>>> print('sizeof(array_test1) ==', sizeof(array_test1))
sizeof(array_test1) == 530929
>>> print('sizeof(array_test2) ==', sizeof(array_test2))
sizeof(array_test2) == 194825
>>>
如果您想得到答案,请确保不要给此代码提供任何无限迭代器。
【讨论】:
"Yield from" 这不是 python3 特有的语法吗? 是的,因为其余代码可能会通过2to3.py
运行。移植到yield from
不可用的地方应该相当容易。
str 不应该被迭代来检查它们的单字符子字符串大小,我提出了一个考虑到这一点的编辑。
@Adirio 如果要正确修复此代码,确实需要考虑几个内置函数。我想到了另外两个例子:bytes
和 bytearray
。说到数组,array.array
也不应该被探索。你还能想到多少例外?
你是对的,有多个例外,但 str
一个是相关的,因为您将它们作为 Test1
中的键和两个变量之一。基本上,您为这些字符串中的每个字符添加 26 个额外字节。在每个yield
之前添加print
语句并执行sizeof('Hello')
,您将得到Hello
H
e
l
o
。这会让你的结果偏离目标。以上是关于在 Python 中准确测量对象大小 - Sys.GetSizeOf 不起作用的主要内容,如果未能解决你的问题,请参考以下文章