5

我有一个需要用 0-3 个字符串“标记”的对象(在一组 20 种可能性中);这些值都是唯一的,顺序无关紧要。唯一需要对标签执行的操作是检查特定标签是否存在 ( specific_value in self.tags)。

但是,内存中同时存在大量这些对象,以至于它突破了我旧计算机 RAM 的限制。所以节省几个字节可以加起来。

由于每个对象上的标签如此之少,我怀疑查找时间是否很重要。但是:在这里使用元组和冻结集之间是否存在内存差异?还有其他真正的理由使用其中一个吗?

4

4 回答 4

6

元组非常紧凑。集合基于哈希表,并且依赖于具有“空”插槽来降低哈希冲突的可能性。

对于最新版本的 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 个块的大小类是我们想要的;-) 每个块消耗 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 的小对象分配器实际需要使用的字节数。

例如,

>>> sys.getsizeof("a")
50

在 32 位盒子上,实际上需要使用 52 个字节,以提供 4 字节对齐。在 64 位的盒子上,目前需要 8 字节对齐,因此需要使用 56 字节。在 Python 3.8(尚未发布)下,在 64 位框上需要 16 字节对齐,并且需要使用 64 字节。

但是忽略这一切,一个元组总是比任何形式的具有相同数量元素的集合需要更少的内存 - 甚至比具有相同数量元素的列表更少。

于 2019-07-07T05:16:41.243 回答
4

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

所有内置对象都将返回正确的结果,但这对于第三方扩展不一定适用,因为它是特定于实现的。

...所以不要对这个解决方案感到满意

编辑:显然@TimPeters 的答案更正确......

于 2019-07-07T04:57:40.923 回答
2

如果您想节省内存,请考虑

  • 通过将标签存在的数据结构提取到外部(单例)数据结构中,牺牲一些优雅来节省一些内存
  • 使用“标志”(位图)类型的方法,其中每个标签都映射到一个 32 位整数的位。然后,您所需要的只是dict从对象(身份)到 32 位整数(标志)的(单例)映射。如果不存在标志,则字典中没有条目。
于 2019-07-07T05:02:14.533 回答
1

`如果用记录类库中的类型替换元组,可能会减少内存

>>> 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)

PS:这些数字是在 64 位 Python 3.8 上测量的。

于 2020-04-18T19:39:41.990 回答