我有一个python的pickle对象,它生成一个180 Mb的文件。当我取消它时,内存使用量会爆炸到2或3Gb。你有类似的经历吗?这是正常的吗?
对象是包含字典的树:每个边是一个字母,每个节点都是一个潜在的单词。因此,要存储一个单词,您需要的边数与该单词的长度一样多。所以,第一级是最多26个节点,第二个是26 ^ 2,第三个是26 ^ 3等...对于每个节点都是一个单词我有一个属性指向关于单词的信息(动词,名词,定义等...)。
我的单词最多约40个字符。我有大约五十万条款。一切顺利,直到我腌制(使用简单的cpickle转储):它提供180 Mb文件。 我在Mac OS上,当我取消这些180 Mb时,操作系统为python进程提供了2或3 Gb的“内存/虚拟内存”:(
我在这棵树上看不到任何递归:边缘的节点本身就是一个数组数组。不涉及递归。
我有点卡住:这些180 Mb的加载大约是20秒(没有谈到内存问题)。我不得不说我的CPU不是那么快:核心i5,1.3Ghz。但我的硬盘是ssd。我只有4Gb的内存。
要在我的树中添加这500 000个单词,我会阅读大约7 000个文件,每个文件包含大约100个单词。这个读取使得mac os分配的内存高达15 Gb,主要是在虚拟内存上:(我一直在使用“with”语句确保关闭每个文件,但实际上并没有帮助。阅读文件带走对于40 Ko,0.2秒。对我来说似乎很长。将它添加到树上的速度要快得多(0.002秒)。
最后我想创建一个对象数据库,但我猜python并不适合。也许我会选择MongoDB :(
class Trie():
"""
Class to store known entities / word / verbs...
"""
longest_word = -1
nb_entree = 0
def __init__(self):
self.children = {}
self.isWord = False
self.infos =[]
def add(self, orthographe, entree):
"""
Store a string with the given type and definition in the Trie structure.
"""
if len(orthographe) >Trie.longest_word:
Trie.longest_word = len(orthographe)
if len(orthographe)==0:
self.isWord = True
self.infos.append(entree)
Trie.nb_entree += 1
return True
car = orthographe[0]
if car not in self.children.keys():
self.children[car] = Trie()
self.children[car].add(orthographe[1:], entree)
答案 0 :(得分:3)
Python对象,特别是在64位计算机上,非常大。当被腌制时,对象获得适合于磁盘文件的非常紧凑的表示。这是一个拆解的泡菜的例子:
>>> pickle.dumps({'x':'y','z':{'x':'y'}},-1)
'\x80\x02}q\x00(U\x01xq\x01U\x01yq\x02U\x01zq\x03}q\x04h\x01h\x02su.'
>>> pickletools.dis(_)
0: \x80 PROTO 2
2: } EMPTY_DICT
3: q BINPUT 0
5: ( MARK
6: U SHORT_BINSTRING 'x'
9: q BINPUT 1
11: U SHORT_BINSTRING 'y'
14: q BINPUT 2
16: U SHORT_BINSTRING 'z'
19: q BINPUT 3
21: } EMPTY_DICT
22: q BINPUT 4
24: h BINGET 1
26: h BINGET 2
28: s SETITEM
29: u SETITEMS (MARK at 5)
30: . STOP
如您所见,它非常紧凑。如果可能的话,什么都不重复。
但是,在内存中,一个对象由相当多的指针组成。让我们问一下Python empty 字典有多大(64位机器):
>>> {}.__sizeof__()
248
哇! 空字典的248个字节!请注意,字典预先分配了最多八个元素的空间。但是,即使字典中有一个元素,也要支付相同的内存成本。
类实例包含一个用于保存实例变量的字典。您的尝试为孩子们添加了一个额外的字典。因此,每个实例花费您近500字节。估计有2-4百万个Trie对象,您可以轻松查看内存使用的来源。
您可以通过向Trie添加__slots__
来消除实例字典,从而缓解此问题。你这样做可能会节省大约750MB(我的猜测)。它会阻止你向Trie添加更多变量,但这可能不是一个大问题。
答案 1 :(得分:2)
你真的需要它一次性加载或转储所有内存吗?如果您不需要内存中的所有内容,但只需要在任何给定时间选择所需的部分,您可能希望将字典映射到磁盘上的一组文件而不是单个文件...或将字典映射到a数据库表。因此,如果您正在寻找能够将大型数据字典保存到磁盘或数据库的东西,并且可以利用酸洗和编码(编解码器和散列图),那么您可能需要查看klepto
。
klepto
提供了用于写入数据库的字典抽象,包括将文件系统视为数据库(即将整个字典写入单个文件,或将每个条目写入其自己的文件)。对于大数据,我经常选择将字典表示为我的文件系统上的目录,并将每个条目都作为文件。 klepto
还提供缓存算法,因此如果您使用字典的文件系统后端,则可以通过利用内存缓存来避免速度损失。
>>> from klepto.archives import dir_archive
>>> d = {'a':1, 'b':2, 'c':map, 'd':None}
>>> # map a dict to a filesystem directory
>>> demo = dir_archive('demo', d, serialized=True)
>>> demo['a']
1
>>> demo['c']
<built-in function map>
>>> demo
dir_archive('demo', {'a': 1, 'c': <built-in function map>, 'b': 2, 'd': None}, cached=True)
>>> # is set to cache to memory, so use 'dump' to dump to the filesystem
>>> demo.dump()
>>> del demo
>>>
>>> demo = dir_archive('demo', {}, serialized=True)
>>> demo
dir_archive('demo', {}, cached=True)
>>> # demo is empty, load from disk
>>> demo.load()
>>> demo
dir_archive('demo', {'a': 1, 'c': <built-in function map>, 'b': 2, 'd': None}, cached=True)
>>> demo['c']
<built-in function map>
>>>
klepto
还有其他标记,例如compression
和memmode
,可用于自定义数据的存储方式(例如压缩级别,内存映射模式等)。
使用(MySQL等)数据库作为后端而不是文件系统同样容易(相同的界面)。您还可以关闭内存缓存,因此只需设置cached=False
,每次读/写都会直接进入存档。
klepto
还提供了许多缓存算法(如mru
,lru
,lfu
等),以帮助您管理内存缓存,并且使用算法执行转储并加载到归档后端。
您可以使用标志cached=False
完全关闭内存缓存,并直接读写磁盘或数据库。如果您的条目足够大,您可以选择写入磁盘,将每个条目放在其自己的文件中。这是两个例子。
>>> from klepto.archives import dir_archive
>>> # does not hold entries in memory, each entry will be stored on disk
>>> demo = dir_archive('demo', {}, serialized=True, cached=False)
>>> demo['a'] = 10
>>> demo['b'] = 20
>>> demo['c'] = min
>>> demo['d'] = [1,2,3]
然而,虽然这会大大减少加载时间,但它可能会使整体执行速度降低一些......通常最好指定内存缓存中保留的最大数量并选择一个好的缓存算法。您必须使用它来获得满足您需求的正确平衡。
在此处获取klepto
:https://github.com/uqfoundation