12

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

附录

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

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

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

PS:我不想使用 Lucene 或 Xapian ...这个问题是指存储倒排索引。我不得不问一个新问题,因为我无法删除前一个问题。

4

5 回答 5

13

使用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 更有效。

指定否定协议版本选择支持的最高协议版本。使用的协议越高,读取生成的 pickle 所需的 Python 版本越新。

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

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

于 2010-10-18T09:33:51.680 回答
7

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

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

klepto到这里:https : //github.com/uqfoundation

于 2014-08-11T13:42:40.720 回答
3

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

  • 协议版本 0 是原始的“人类可读”协议,向后兼容早期版本的 Python。
  • 协议版本 1 是一种旧的二进制格式,它也与早期版本的 Python 兼容。
  • 协议版本 2 是在 Python 2.3 中引入的。它提供了更有效的新型类的酸洗。有关协议 2 带来的改进的信息,请参阅 PEP 307。
  • Python 3.0 中添加了协议版本 3 。它对字节对象有明确的支持,并且不能被 Python 2.x 解压。这是默认协议,当需要与其他 Python 3 版本兼容时推荐使用的协议。
  • Python 3.4 中添加了协议版本 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()

也就是说,在 2010 年,该json模块的编码速度比pickle. 我的 2014 年基准测试显示marshal>> ,但与特定的 Python 版本相结合picklejsonmarshal's

于 2014-11-11T12:42:24.053 回答
0

您是否尝试过使用YAMLJSON等替代存储格式?Python 使用我认为的模块从 Python 2.6 原生支持 JSON json,并且有用于 YAML 的第三方模块

你也可以试试这个shelve模块。

于 2010-10-18T09:16:10.273 回答
0

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

于 2010-10-18T09:36:26.917 回答