繁体   English   中英

使用python pickle加载大字典

[英]Loading a large dictionary using python pickle

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

{word : { doc_name : [location_list] } }

例如,让字典称为索引,然后对于单词“spam”,条目看起来像:

{ 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) ,pickle文件相当小 - 大约237 MB(转储需要300秒)......现在需要一半的时间来加载(61秒......相反)到112秒之前.... time.time()

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

至于现在,我正在接受蒂姆的回答。

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

使用cPickle.dump / cPickle.dumps时尝试使用protocol参数。 来自cPickle.Pickler.__doc__

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

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

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

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

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

转换JSON或YAML可能需要比大多数时间的酸洗更长的时间 - pickle存储本机Python类型。

你真的需要它一次加载吗? 如果您不需要内存中的所有内容,但只需要在任何给定时间选择所需的部分,您可能希望将字典映射到磁盘上的一组文件而不是单个文件...或将字典映射到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通过构建自定义键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]

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

在这里获取kleptohttps//github.com/uqfoundation

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

  • 协议版本0是原始的“人类可读”协议,并且向后兼容早期版本的Python。
  • 协议版本1是旧的二进制格式,它也与早期版本的Python兼容。
  • 在Python 2.3中引入了协议版本2。 它提供了更有效的新式类型的酸洗。 有关协议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模块是快25倍的编码和在比解码简单类型快15倍pickle 我的2014年基准测试marshal > pickle > json ,但是marshal's与特定的Python版本相结合

您是否尝试过使用其他存储格式,例如YAMLJSON 我认为Python使用json模块本地支持来自Python 2.6的json ,并且YAML还有第三方模块

您也可以尝试shelve模块。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM