代码之家  ›  专栏  ›  技术社区  ›  easysid

使用python pickle加载大型字典

  •  9
  • easysid  · 技术社区  · 14 年前

    我有一个完整的反向索引,以嵌套的python字典的形式出现。其结构为:

    {word : { doc_name : [location_list] } }
    

    例如,让字典名为index,然后对于单词“spam”,条目将如下所示:

    { spam : { doc1.txt : [102,300,399], doc5.txt : [200,587] } }
    

    我使用这个结构,因为python dict非常优化,它使编程更容易。

    对于任何单词“spam”,包含它的文档可以通过以下方式给出:

    index['spam'].keys()
    

    文档文档1的发布列表:

    index['spam']['doc1']
    

    目前我正在使用cpickle来存储和加载这本词典。但是pickled文件大约是380MB,加载需要很长时间-112秒(大约我用 时间,时间() )内存使用率达到1.2 GB(GNOME系统监视器)。一旦装车,就没事了。我有4GB RAM。

    len(index.keys()) 给出229758

    代码

    import cPickle as pickle
    
    f = open('full_index','rb')
    print 'Loading index... please wait...'
    index = pickle.load(f)  # This takes ages
    print 'Index loaded. You may now proceed to search'
    

    我怎样才能使它加载更快? 当应用程序启动时,我只需要加载一次。之后,访问时间对于响应查询很重要。

    我应该切换到像sqlite这样的数据库并在其键上创建索引吗?如果是,如何存储值以具有等效模式,这使得检索变得容易。还有什么需要我调查的吗?

    补遗

    使用蒂姆的答案 pickle.dump(index, file, -1) pickled文件要小得多-大约237 MB(转储需要300秒)。现在需要一半的时间加载(61秒…与之前的112秒不同…. 时间,时间() )

    但为了可伸缩性,我应该迁移到数据库吗?

    至于现在,我认为蒂姆的回答是可以接受的。

    附言:我不想用Lucene或Xapian… 这个问题是指 Storing an inverted index . 我不得不问一个新问题,因为我不能删除前一个问题。

    5 回复  |  直到 7 年前
        1
  •  12
  •   Tim McNamara    14 年前

    使用时尝试协议参数 cPickle.dump / cPickle.dumps . 从 cPickle.Pickler.__doc__ :

    pickler(文件,协议=0)--创建pickler。

    这需要一个类似文件的对象来写入pickle数据流。 可选的proto参数告诉pickler使用给定的 协议;支持的协议是0、1、2。默认值 协议为0,向后兼容。(协议0是 只能写入以文本方式打开的文件的协议 模式和读取成功。当使用高级协议时 如果不是0,请确保以二进制模式打开文件,这两种情况都是在 腌渍和去皮。)

    协议1比协议0更有效;协议2是 比协议1更有效。

    指定负协议版本将选择最高的 支持协议版本。使用的协议越高, 更新的python版本需要读取pickle 产生。

    文件参数必须有一个write()方法,该方法接受一个 字符串参数。因此,它可以是一个打开的文件对象,一个字符串 对象,或满足此接口的任何其他自定义对象。

    转换JSON或YAML可能要比酸洗花费更长的时间——pickle存储本地python类型。

        2
  •  5
  •   Mike McKerns    10 年前

    你真的需要它同时加载吗?如果您不需要在内存中全部存储,而只需要在任何给定时间选择所需的部分,您可能希望将字典映射到磁盘上的一组文件,而不是单个文件,或者将dict映射到数据库表。因此,如果您正在寻找一些可以将大的数据字典保存到磁盘或数据库中,并且可以使用pickle和编码(codecs和hashmaps),那么您可能需要查看 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>
    >>> 
    

    卡普托 还具有其他标志,如 compression memmode 可用于自定义数据存储方式(例如压缩级别、内存映射模式等)。 使用(mysql等)数据库作为后端(而不是文件系统)也同样容易(完全相同的接口)。您还可以关闭内存缓存,因此每次读/写都直接转到存档,只需设置 cached=False .

    卡普托 通过构建自定义的 keymap .

    >>> from klepto.keymaps import *
    >>> 
    >>> s = stringmap(encoding='hex_codec')
    >>> x = [1,2,'3',min]
    >>> s(x)
    '285b312c20322c202733272c203c6275696c742d696e2066756e6374696f6e206d696e3e5d2c29'
    >>> p = picklemap(serializer='dill')
    >>> p(x)
    '\x80\x02]q\x00(K\x01K\x02U\x013q\x01c__builtin__\nmin\nq\x02e\x85q\x03.'
    >>> sp = s+p
    >>> sp(x)
    '\x80\x02UT28285b312c20322c202733272c203c6275696c742d696e2066756e6374696f6e206d696e3e5d2c292c29q\x00.' 
    

    卡普托 还提供了许多缓存算法(如 mru , lru , lfu 等),以帮助您管理内存缓存,并将使用该算法为您进行转储和加载到存档后端。

    你可以用旗子 缓存=错误 完全关闭内存缓存,直接读写磁盘或数据库。如果您的条目足够大,您可以选择写入磁盘,将每个条目放入自己的文件中。这是一个既能做到这一点的例子。

    >>> 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]
    

    然而,尽管这将大大减少加载时间,但可能会使总体执行速度减慢一点,通常最好指定内存缓存中要保存的最大数量,并选择一个好的缓存算法。你必须玩弄它才能得到满足你需要的平衡。

    得到 卡普托 在这里: https://github.com/uqfoundation

        3
  •  3
  •   Community CDub    7 年前

    python 2.x中的一个常见模式是使用纯python实现一个模块的一个版本,可选的加速版本作为C扩展实现;例如, pickle cPickle . 这就增加了导入加速版本的负担,并使这些模块的每个用户都回到纯Python版本上。 在Python 3中 ,加速版本被视为纯Python版本的实现细节。 用户应始终导入标准版本,该版本尝试导入加速版本并返回纯Python版本。 The pickle / cPickle pair received this treatment.

    • 协议版本0是原始的人类可读协议,与早期版本的python向后兼容。
    • 协议版本1是一种旧的二进制格式,它也与早期版本的python兼容。
    • python2.3中引入了协议版本2。它提供了更有效的新类型的酸洗。有关方案2所带来的改进的信息,请参阅PEP 307。
    • Protocol version 3 在python 3.0中添加。它对bytes对象有明确的支持,不能被python 2.x解压。
    • 在python 3.4中添加了协议版本4。它增加了对超大对象的支持 PEP 3154 有关协议4所带来的改进的信息。

    如果您的字典很大,并且只应与python 3.4或更高版本兼容,请使用:

    pickle.dump(obj, file, protocol=4)
    pickle.load(file, encoding="bytes")
    

    或:

    Pickler(file, 4).dump(obj)
    Unpickler(file).load()
    

    这就是说, in 2010 这个 json 泡菜 . My 2014 benchmark marshal gt; 泡菜 gt; 杰森 但是 marshal's coupled to specific Python versions .

        4
  •  0
  •   Tamás    14 年前

    是否尝试使用其他存储格式,如 YAML JSON ?python使用 json 我想模块,还有 third party modules for YAML .

    您也可以尝试 shelve 模块。

        5
  •  0
  •   knitti freethinker    14 年前

    取决于“长”多长时间,您必须考虑必须进行的权衡:要么在(长)启动后在内存中准备好所有数据,要么只加载部分数据(然后需要将数据拆分为多个文件,或者使用sqlite或类似的方法)。我怀疑预先将所有数据(如sqlite)加载到字典中会带来任何改进。