Python 内存序列化

10 投票
2 回答
548 浏览
提问于 2025-04-16 17:52

我在想,是否有人知道以下问题的答案。

我正在用Python构建一个基于字符的后缀树。这个树里有超过1100万个节点,占用了大约3GB的内存。通过使用slot类的方法,而不是Dict方法,我把内存使用量从7GB减少到了3GB。

当我把这个树序列化(使用最高的协议)时,生成的文件小得多,缩小了超过一百倍。

但是,当我把这个序列化的文件加载回来的时候,它又消耗了3GB的内存。这额外的内存开销是从哪里来的?是不是和Python处理类实例的内存引用有关?

更新

感谢larsmans和Gurgeh给出的非常有帮助的解释和建议。我正在把这个树用作一个信息检索接口,处理一堆文本。

我最开始把孩子节点(最多30个)存储为Numpy数组,然后尝试了硬件版本(ctypes.py_object*30)、Python数组(ArrayType),还有字典和集合类型。

使用列表似乎效果更好(我用guppy来分析内存,并使用__slots__['variable',...]),但我仍然在尝试进一步压缩内存。如果能的话,我希望能再减少一些。使用数组时唯一的问题是必须提前指定它们的大小,这导致了在只有一个孩子的节点上有些冗余,而我有很多这样的节点。;-)

在树构建完成后,我打算进行第二次处理,把它转换为一个概率树,但也许我可以在构建树的时候就做到这一点。因为在我这个情况下,构建时间不是特别重要,所以array.array()听起来是个值得尝试的东西,感谢这个建议,真的很感激。

我会告诉你结果如何。

2 个回答

3

你是先建好树,然后就一直用,不再改动吗?如果是这样的话,你可以考虑分开使用不同的结构来处理动态构建和静态使用。

字典和对象在动态修改方面表现很好,但在只读的情况下,它们的空间利用效率就不高了。我不太清楚你是用后缀树做什么的,但你可以让每个节点用一个包含排序数组和同样长度的子节点元组的二元组来表示(用元组而不是向量是为了避免多分配空间)。你可以使用bisect模块来遍历树,查找数组中的元素。数组中字符的索引会对应到子节点元组中的一个子节点。这样你就可以避免使用字典、对象和向量。

在构建过程中,你也可以做类似的事情,可能用子节点向量代替子节点元组。但这样会让构建变得更慢,因为在有序向量中插入新节点的时间复杂度是O(N)。

9

如果你尝试把一个空列表进行序列化(也就是“腌制”),你会得到:

>>> s = StringIO()
>>> pickle.dump([], s)
>>> s.getvalue()
'(l.'

同样地,空的字典(dict)也会得到类似的结果,显示为'(d.'。这总共是三字节。不过,实际上在内存中,列表的表示方式包含了很多信息:

  • 一个引用计数
  • 一个类型ID,这里面有指向类型名称的指针和内存分配的管理信息
  • 一个指向实际元素的指针数组
  • 还有更多的管理信息。

在我的电脑上,使用的是64位指针,Python列表的头部对象大小是40字节,所以这差不多是一个数量级。我猜空的字典(dict)的大小也差不多。

此外,列表和字典都采用了一种超分配策略,以便在主要操作上实现接近常数时间的性能(也就是O(1)),而且在使用malloc时会有额外的开销,还有对齐、成员属性等各种因素,这些都会让它们的大小增加一个数量级。

总结一下:对于Python对象来说,序列化是一种相当不错的压缩算法 :)

撰写回答