我想知道是否有人可能知道以下答案。
我正在使用Python构建基于字符的后缀树。树中有超过1100万个节点,可以容纳大约3GB的内存。使用插槽类方法而不是 Dict 方法,这比7GB低。
当我对树进行序列化时(使用最高协议),生成的文件要小一百倍。
当我重新加载pickle文件时,它再次消耗3GB内存。这个额外开销来自何处,是否与Pythons处理对类实例的内存引用有关?
更新
谢谢larsmans和Gurgeh的非常有用的解释和建议。我正在使用树作为文本语料库中信息检索界面的一部分。
我最初将子项(最多30个)存储为Numpy数组,然后尝试了硬件版本(ctypes.py_object*30
),Python数组(ArrayType
),以及字典和Set类型。
列表似乎做得更好(使用孔雀鱼来描述记忆,__slots__['variable',...]
),但如果可以的话,我仍然试图将它压缩一点。我对数组唯一的问题是必须事先指定它们的大小,这导致只有一个子节点的节点有点冗余,而且我有很多它们。 ; - )
在构造树之后,我打算将它转换为具有第二遍的概率树,但也许我可以在构造树时执行此操作。由于构造时间在我的情况下并不太重要,所以array.array()听起来像是有用的东西,感谢提示,非常感谢。
我会告诉你它是怎么回事。
答案 0 :(得分:9)
如果你试图挑选一个空列表,你会得到:
>>> s = StringIO()
>>> pickle.dump([], s)
>>> s.getvalue()
'(l.'
,类似'(d.'
表示空dict
。这是三个字节。但是,in-memory representation of a list包含
在我的机器上,它有64位指针,sizeof
一个Python列表头对象是40个字节,所以这是一个数量级。我假设一个空的dict
具有相似的大小。
然后,list
和dict
都使用了一个分配策略来获取amortized O(1) performance的主要操作,malloc
引入了开销,有对齐,成员属性可以或者可能甚至没有意识到和其他各种因素可以使你达到第二个数量级。
总结:pickle是一个非常好的Python对象压缩算法:)
答案 1 :(得分:3)
您是否构建了一次树,然后使用它而不进一步修改它?在这种情况下,您可能需要考虑为动态构造和静态用法使用单独的结构。
Dicts和对象非常适合动态修改,但在只读方案中它们的空间效率不高。我不确切知道你使用后缀树是什么,但你可以让每个节点由一个排序的array.array('c')的2元组和一个同样长的子节点元组(一个元组代替向量以避免过度分配)。您使用bisect-module遍历树以在数组中进行查找。数组中字符的索引将对应于子节点元组中的子节点。这样就可以避免使用dicts,objects和vector。
您可以在构建过程中执行类似操作,可能使用子节点向量而不是子节点元组。但这当然会使构造变慢,因为在排序向量中插入新节点是O(N)。