加载pickle python对象会占用大量内存

时间:2014-08-14 14:11:48

标签: python memory pickle

我有一个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)

2 个答案:

答案 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还有其他标记,例如compressionmemmode,可用于自定义数据的存储方式(例如压缩级别,内存映射模式等)。 使用(MySQL等)数据库作为后端而不是文件系统同样容易(相同的界面)。您还可以关闭内存缓存,因此只需设置cached=False,每次读/写都会直接进入存档。

klepto还提供了许多缓存算法(如mrulrulfu等),以帮助您管理内存缓存,并且使用算法执行转储并加载到归档后端。

您可以使用标志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]

然而,虽然这会大大减少加载时间,但它可能会使整体执行速度降低一些......通常最好指定内存缓存中保留的最大数量并选择一个好的缓存算法。您必须使用它来获得满足您需求的正确平衡。

在此处获取kleptohttps://github.com/uqfoundation