【问题标题】:Loading a large dictionary using python pickle使用 python pickle 加载一个大字典
【发布时间】:2011-04-26 20:16:49
【问题描述】:

我有一个嵌套 python 字典形式的完整倒排索引。它的结构是:

{word : { doc_name : [location_list] } }

例如让字典被称为索引,那么对于一个单词“垃圾邮件”,条目看起来像:

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

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

对于任何单词“垃圾邮件”,包含它的文档可以通过:

index['spam'].keys()

以及文档 doc1 的发布列表:

index['spam']['doc1']

目前我正在使用 cPickle 来存储和加载这个字典。但是腌制文件大约 380 MB 并且需要很长时间才能加载 - 112 秒(大约。我使用 time.time() 对其进行计时)并且内存使用量达到 1.2 GB(Gnome 系统监视器) .一旦它加载,它的罚款。我有 4GB 内存。

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 这样的数据库并在其键上创建索引吗?如果是,我如何存储值以具有等效的模式,这使得检索变得容易。还有什么我应该研究的吗?

附录

使用 Tim 的回答 pickle.dump(index, file, -1),腌制文件要小得多 - 大约 237 MB(转储需要 300 秒)......现在加载时间减少了一半(61 秒......而不是之前的 112 秒。 ... time.time())

但是我应该迁移到数据库以获得可扩展性吗?

至于现在,我将 Tim 的回答标记为已接受。

PS:我不想使用 Lucene 或 Xapian ... 这个问题指的是Storing an inverted index。我不得不提出一个新问题,因为我无法删除前一个问题。

【问题讨论】:

  • @S.Lott : 添加时间细节
  • “但是我应该迁移到数据库以获得可扩展性吗?”只有您可以决定加载时间是否有问题。你多久加载一次?与加载所需的时间相比,您运行了多少查询?这是一个需要事实的工程问题。
  • @S.Lott:我想试试数据库实现。这将是一个很好的比较。我还有很多时间来完成它。我现在遇到的问题是将索引存储在数据库中。我认为字典实现是一个好主意,但这在数据库中是不可能的。我阅读了这些关于数据库存储的帖子stackoverflow.com/questions/1571140/…、stackoverflow.com/questions/3581792/…,但它们并没有太大帮助。
  • “字典实现是个好主意,但在数据库中这是不可能的”。错误的。获得更好的 ORM。

标签: python pickle inverted-index


【解决方案1】:

Python 2.x 中的一个常见模式是在纯 Python 中实现一个模块版本,并以 C 扩展实现可选的加速版本;例如,pickle 和 cPickle。这将导入加速版本和退回到这些模块的每个用户的纯 Python 版本的负担。 在 Python 3.0 中,加速版本被视为纯 Python 版本的实现细节。 用户应始终导入标准版本,它会尝试导入加速版本并回退到纯 Python 版本。 The pickle / cPickle pair received this treatment.

  • 协议版本 0 是原始的“人类可读”协议,向后兼容早期版本的 Python。
  • 协议版本 1 是一种旧的二进制格式,也与早期版本的 Python 兼容。
  • 协议版本 2 是在 Python 2.3 中引入的。它提供了更有效的新型类的酸洗。有关协议 2 带来的改进的信息,请参阅 PEP 307。
  • Protocol version 3 在 Python 3.0 中添加。它对字节对象有明确的支持,并且不能被 Python 2.x 解压。 这是默认协议,当需要与其他 Python 3 版本兼容时推荐使用的协议。
  • 协议版本 4 是在 Python 3.4 中添加的。它增加了对超大对象的支持,酸洗更多种类的对象,以及一些数据格式优化。有关协议 4 带来的改进的信息,请参阅 PEP 3154。

如果您的字典很大并且只应与 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 模块的编码速度比 pickle 快 25 倍,解码简单类型的速度快 15 倍。 My 2014 benchmark 说marshal > pickle > json,但是marshal's coupled to specific Python versions。

【讨论】:

    【解决方案2】:

    您真的需要一次加载所有内容吗?如果您不需要在内存中的所有内容,而只需要在任何给定时间选择您想要的部分,您可能希望将字典映射到磁盘上的一组文件而不是单个文件......或者将字典映射到数据库表。因此,如果您正在寻找可以将大型数据字典保存到磁盘或数据库,并且可以利用酸洗和编码(编解码器和哈希图)的东西,那么您可能需要查看 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 通过构建自定义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.' 
    

    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

    【讨论】:

    • +1 这很棒。我们在this thread 中有一个与此相关的问题,我们想知道您是否可以提供帮助。
    【解决方案3】:

    取决于“长”多长时间,你必须考虑你必须做出的权衡:要么在(长时间)启动后在内存中准备好所有数据,要么只加载部分数据(然后你需要拆分多个文件中的日期或使用 SQLite 或类似的东西)。我怀疑从例如预先加载所有数据将sqlite放入字典会带来任何改进。

    【讨论】:

      【解决方案4】:

      在使用cPickle.dump/cPickle.dumps 时尝试协议参数。来自cPickle.Pickler.__doc__:

      Pickler(file, protocol=0) -- 创建一个pickler。

      这需要一个类似文件的对象来写入pickle数据流。 可选的 proto 参数告诉 pickler 使用给定的 协议;支持的协议为 0、1、2。默认 协议为0,向后兼容。 (协议 0 是 唯一可以写入以文本方式打开的文件的协议 模式并成功回读。当使用更高的协议时 大于 0,请确保文件以二进制模式打开,当 酸洗和解酸。)

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

      指定一个否定的协议版本选择最高的 支持的协议版本。使用的协议越高, 更新的 Python 版本需要读取泡菜 生产出来的。

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

      在大多数情况下,转换 JSON 或 YAML 可能比 pickle 花费更长的时间 - pickle 存储原生 Python 类型。

      【讨论】:

      • 谢谢!使用 pickle.dump(obj,file,-1),文件更小,加载更快。在问题中添加了时间的详细信息。
      • 这是 Python 3 中默认的兼容性步骤。
      【解决方案5】:

      您是否尝试过使用其他存储格式,例如 YAML 或 JSON? Python 使用我认为的 json 模块从 Python 2.6 原生支持 JSON,并且有 third party modules for YAML。

      您也可以尝试shelve 模块。

      【讨论】:

      • @塔马斯。不,我没有尝试过使用 YAML 或 JSON。我读过关于在我的问题中使用搁置,但据我所知,搁置对于像我这样的大文件表现不佳。
      猜你喜欢
      • 1970-01-01
      • 2012-09-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-04
      • 1970-01-01
      相关资源
      最近更新 更多