哪个占用更少的内存,冻结集或元组?
Which takes less memory, a frozenset or a tuple?
我有一个对象需要 "tagged" 具有 0-3 个字符串(在一组 20 种可能性中);这些值都是唯一的,顺序无关紧要。唯一需要对标签执行的操作是检查特定标签是否存在 (specific_value in self.tags
)。
但是,内存中同时存在大量这些对象,以至于它突破了我旧计算机 RAM 的极限。所以节省几个字节可以加起来。
每个对象上的标签都很少,我怀疑查找时间是否会很重要。但是:这里使用 tuple 和 frozenset 在内存上有区别吗?有没有其他真正的理由使用一个而不是另一个?
sys.getsizeof
似乎是您想要的 stdlib
选项...但我对您的整个用例感到不安
import sys
t = ("foo", "bar", "baz")
f = frozenset(("foo","bar","baz"))
print(sys.getsizeof(t))
print(sys.getsizeof(f))
https://docs.python.org/3.7/library/sys.html#sys.getsizeof
All built-in objects will return correct results, but this does not have to hold true for third-party extensions as it is implementation specific.
...所以不要对这个解决方案感到满意
编辑:显然@TimPeters 的回答更正确...
如果您想节省内存,请考虑
- 通过将标签存在的数据结构提取到外部(单例)数据结构中,以牺牲一些优雅来节省一些内存
- 使用 "flags"(位图)类型的方法,其中每个标签都映射到 32 位整数中的一位。那么你所需要的只是一个从对象(身份)到 32 位整数(标志)的(单例)
dict
映射。如果没有标志,则字典中没有条目。
元组非常紧凑。集合基于哈希表,并依赖于 "empty" 个槽来降低哈希冲突的可能性。
对于足够新的 CPython 版本,sys._debugmallocstats()
显示了许多可能有趣的信息。这里下一个64位Python 3.7.3:
>>> from sys import _debugmallocstats as d
>>> tups = [tuple("abc") for i in range(1000000)]
tuple("abc")
创建一个包含 3 个 1 字符字符串的元组,('a', 'b', 'c')
。在这里我将编辑掉几乎所有的输出:
>>> d()
Small block threshold = 512, in 64 size classes.
class size num pools blocks in use avail blocks
----- ---- --------- ------------- ------------
...
8 72 17941 1004692 4
自从我们创建了一百万个元组以来,使用 1004692 个块的大小 class 是我们想要的一个很好的赌注;-) 每个块占用 72 个字节。
改为切换到 frozensets,输出显示它们每个消耗 224 字节,多出 3 倍多一点:
>>> tups = [frozenset(t) for t in tups]
>>> d()
Small block threshold = 512, in 64 size classes.
class size num pools blocks in use avail blocks
----- ---- --------- ------------- ------------
...
27 224 55561 1000092 6
在这种特殊情况下,您得到的另一个答案恰好给出了相同的结果:
>>> import sys
>>> sys.getsizeof(tuple("abc"))
72
>>> sys.getsizeof(frozenset(tuple("abc")))
224
虽然这通常是正确的,但并非总是如此,因为对象可能需要 分配 比实际需要更多的字节,以满足硬件对齐要求。 getsizeof()
对此一无所知,但 _debugmallocstats()
显示了 Python 的 small-object 分配器实际需要使用的字节数。
例如,
>>> sys.getsizeof("a")
50
在32位的盒子上,实际需要使用52个字节,来提供4字节对齐。在 64 位的盒子上,目前需要 8 字节对齐,所以需要使用 56 字节。在Python 3.8(尚未发布)下,在64位框上需要16字节对齐,并且需要使用64字节。
但忽略所有这些,元组总是需要比具有相同数量元素的任何形式的集合更少的内存 - 甚至比具有相同数量元素的列表更少。
`如果用 recordclass 库中的类型替换元组,则有可能减少内存:
>>> from recordclass import make_arrayclass
>>> Triple = make_arrayclass("Triple", 3)
>>> from sys import getsizeof as sizeof
>>> sizeof(Triple("ab","cd","ef"))
40
>>> sizeof(("ab","cd","ef"))
64
差值等于sizeof(PyGC_Head)
+ sizeof(Py_ssize_t)
.
P.S.: 数字是在 64 位 Python 3.8.
上测量的
我有一个对象需要 "tagged" 具有 0-3 个字符串(在一组 20 种可能性中);这些值都是唯一的,顺序无关紧要。唯一需要对标签执行的操作是检查特定标签是否存在 (specific_value in self.tags
)。
但是,内存中同时存在大量这些对象,以至于它突破了我旧计算机 RAM 的极限。所以节省几个字节可以加起来。
每个对象上的标签都很少,我怀疑查找时间是否会很重要。但是:这里使用 tuple 和 frozenset 在内存上有区别吗?有没有其他真正的理由使用一个而不是另一个?
sys.getsizeof
似乎是您想要的 stdlib
选项...但我对您的整个用例感到不安
import sys
t = ("foo", "bar", "baz")
f = frozenset(("foo","bar","baz"))
print(sys.getsizeof(t))
print(sys.getsizeof(f))
https://docs.python.org/3.7/library/sys.html#sys.getsizeof
All built-in objects will return correct results, but this does not have to hold true for third-party extensions as it is implementation specific.
...所以不要对这个解决方案感到满意
编辑:显然@TimPeters 的回答更正确...
如果您想节省内存,请考虑
- 通过将标签存在的数据结构提取到外部(单例)数据结构中,以牺牲一些优雅来节省一些内存
- 使用 "flags"(位图)类型的方法,其中每个标签都映射到 32 位整数中的一位。那么你所需要的只是一个从对象(身份)到 32 位整数(标志)的(单例)
dict
映射。如果没有标志,则字典中没有条目。
元组非常紧凑。集合基于哈希表,并依赖于 "empty" 个槽来降低哈希冲突的可能性。
对于足够新的 CPython 版本,sys._debugmallocstats()
显示了许多可能有趣的信息。这里下一个64位Python 3.7.3:
>>> from sys import _debugmallocstats as d
>>> tups = [tuple("abc") for i in range(1000000)]
tuple("abc")
创建一个包含 3 个 1 字符字符串的元组,('a', 'b', 'c')
。在这里我将编辑掉几乎所有的输出:
>>> d()
Small block threshold = 512, in 64 size classes.
class size num pools blocks in use avail blocks
----- ---- --------- ------------- ------------
...
8 72 17941 1004692 4
自从我们创建了一百万个元组以来,使用 1004692 个块的大小 class 是我们想要的一个很好的赌注;-) 每个块占用 72 个字节。
改为切换到 frozensets,输出显示它们每个消耗 224 字节,多出 3 倍多一点:
>>> tups = [frozenset(t) for t in tups]
>>> d()
Small block threshold = 512, in 64 size classes.
class size num pools blocks in use avail blocks
----- ---- --------- ------------- ------------
...
27 224 55561 1000092 6
在这种特殊情况下,您得到的另一个答案恰好给出了相同的结果:
>>> import sys
>>> sys.getsizeof(tuple("abc"))
72
>>> sys.getsizeof(frozenset(tuple("abc")))
224
虽然这通常是正确的,但并非总是如此,因为对象可能需要 分配 比实际需要更多的字节,以满足硬件对齐要求。 getsizeof()
对此一无所知,但 _debugmallocstats()
显示了 Python 的 small-object 分配器实际需要使用的字节数。
例如,
>>> sys.getsizeof("a")
50
在32位的盒子上,实际需要使用52个字节,来提供4字节对齐。在 64 位的盒子上,目前需要 8 字节对齐,所以需要使用 56 字节。在Python 3.8(尚未发布)下,在64位框上需要16字节对齐,并且需要使用64字节。
但忽略所有这些,元组总是需要比具有相同数量元素的任何形式的集合更少的内存 - 甚至比具有相同数量元素的列表更少。
`如果用 recordclass 库中的类型替换元组,则有可能减少内存:
>>> from recordclass import make_arrayclass
>>> Triple = make_arrayclass("Triple", 3)
>>> from sys import getsizeof as sizeof
>>> sizeof(Triple("ab","cd","ef"))
40
>>> sizeof(("ab","cd","ef"))
64
差值等于sizeof(PyGC_Head)
+ sizeof(Py_ssize_t)
.
P.S.: 数字是在 64 位 Python 3.8.
上测量的