哪个需要更少的内存,一个frozenset 还是一个元组?

Dra*_*nis 5 python memory-management tuples frozenset

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

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

每个对象上的标签很少,我怀疑查找时间会很重要。但是:在这里使用元组和frozenset 之间是否存在内存差异?是否有任何其他真正的理由使用一个而不是另一个?

Tim*_*ers 6

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

对于足够新的 CPython 版本,sys._debugmallocstats()显示许多潜在的有趣信息。在 64 位 Python 3.7.3 下:

>>> from sys import _debugmallocstats as d
>>> tups = [tuple("abc") for i in range(1000000)]
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

由于我们创建了 100 万个元组,因此使用 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
Run Code Online (Sandbox Code Playgroud)

在这种特殊情况下,您得到的另一个答案恰好给出了相同的结果:

>>> import sys
>>> sys.getsizeof(tuple("abc"))
72
>>> sys.getsizeof(frozenset(tuple("abc")))
224
Run Code Online (Sandbox Code Playgroud)

虽然这通常是正确的,但并非总是如此,因为对象可能需要分配比实际需要更多的字节,以满足硬件对齐要求。 getsizeof()对此一无所知,但_debugmallocstats()显示了 Python 的小对象分配器实际需要使用的字节数。

例如,

>>> sys.getsizeof("a")
50
Run Code Online (Sandbox Code Playgroud)

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

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

  • 关心什么?;-) 下划线是合适的,因为 CPython 的小对象分配器当然是 CPython 特有的 - 它是一个实现细节,而不是语言本身定义的东西。 (2认同)