【问题标题】:How can I mimic a space efficient version of dict in Python?如何在 Python 中模仿节省空间的 dict 版本?
【发布时间】:2013-10-23 15:05:04
【问题描述】:

我正在研究一个字典结构,其中我有一个文档字典,每个文档都有一个单词字典(其中每个键是 word_id(整数),值是计数),这样:

document_dict = { "doc1": {1:2, 2:10, 10:2, 100: 1}, "doc2": {10:2, 20:10, 30:2, 41: 19},...}

请注意,内部字典非常稀疏,所以即使我有 250K 字,我也不希望每个文档有超过 1K 的键。

在每次迭代中,我需要总结一个单词的字典:计数到一个文档,例如我需要将 {1:2, 2:10, 10:2, 120: 1} 的新字典合并到 "doc1": {1:2, 2:10, 10:2, 100: 1}。

现在,我的实现运行得非常快,但是 2 小时后内存不足(我使用的是 40GB 的服务器)。

我总结关键的方式是这样的:

假设 new_dict 是我要添加到 doc1 的新单词:count 对,例如:

new_dict = {1:2, 2:10, 10:2, 120: 1}
doc1 = {1:2, 2:10, 10:2, 100: 1}

for item in new_dict:
      doc1[item] = doc1.get(item, 0) + new_dict[item]

然后,由于我的字典在很短的时间内变得非常大,所以用字典运行代码根本不可能,我尝试将字典实现为 2 个列表的列表:例如doc1 = [[],[]] 其中第一个列表保存键,第二个键保存值。

现在当我想联合2个这样的结构时,我首先尝试获取doc1中new_dict的每个项目的索引。如果我成功获取了索引,则意味着该键已经在 doc1 中,因此我可以更新相应的值。否则,它还没有在 doc1 中,所以我将新的键和值追加()到列表的末尾。但是这种方法运行速度非常慢(在 dict 版本中,我能够在 2 小时内处理多达 600K 文档,现在我只能在 15 小时内处理 250K 文档)。

所以我的问题是:如果我想使用字典结构 (key, val) 对,我需要在每次迭代中合并 2 个字典的键并求和它们的值,有没有办法更有效地实现这个空间?

【问题讨论】:

  • 我认为问题不在于数据结构;它试图将 X 单位的东西装入一个 Y 单位的袋子中,其中 X > Y。
  • 您可能存在与 dict() 操作的内部方式无关的内存泄漏。您是否使用分析器运行过?我发现这篇文章很有用mg.pov.lt/blog/hunting-python-memleaks.html
  • 什么变得太大了,文档字典?
  • @martineau 实际上,我在字典中存储的文档数量以及我为每个文档存储的 word:count 对都随着时间的推移而增加。
  • 听起来像是一个非常典型的 map-reduce 工作? pythonhosted.org/mrjob

标签: python optimization dictionary


【解决方案1】:

它不一定更节省空间,但我建议使用shelve 模块切换到基于磁盘的字典,这样您就不必一次将整个字典放在内存中。

它们非常易于使用,因为它们支持熟悉的字典界面,如下所示:

import shelve

document_dict = shelve.open('document_dict', writeback=True)

document_dict.update({"doc1": {1:2, 2:10, 10:2, 100: 1},
                      "doc2": {10:2, 20:10, 30:2, 41: 19},
                      "doc3": {1:2, 2:10, 10:2, 100: 1},})

new_dict = {1:2, 2:10, 10:2, 120: 1}
doc = document_dict.get("doc3", {})  # get current value, if any

for item in new_dict:
    doc[item] = doc.get(item, 0) + new_dict[item]   # update version in memory

document_dict["doc3"] = doc  # write modified (or new) entry to disk
document_dict.sync()  #  clear cache

print document_dict

document_dict.close()

输出:

{'doc2': {41: 19, 10: 2, 20: 10, 30: 2},
 'doc3': {120: 1, 1: 4, 2: 20, 100: 1, 10: 4},
 'doc1': {1: 2, 2: 10, 100: 1, 10: 2}}

【讨论】:

  • 谢谢,货架看起来很有希望。但是,我不太确定“您不必一次将整个字典保存在内存中”部分。因为据我了解,搁置会将所有对象保留在内存中(因为 writeback=True)除非我调用同步或关闭调用,对吗?即使我偶尔调用一次sync()(例如,在每100个文档中,我可以调用sync并清除缓存),然后我不确定如何确保我正在访问对象X的真实值.比如这个例子:stackoverflow.com/a/3410066
  • 是的,在使用 writeback 选项时调用 sync() 会清除缓存。您可以确保您拥有对象的真实值,并通过在更新文档的计数后调用它来保持内存中缓存的数据量很小,如我更新的答案所示。
  • 谢谢,它运行良好,我能够毫无问题地运行我的程序!
  • 很高兴听到这个消息。速度对比如何?根据您正在做什么,可能(并且值得)通过不经常同步和清除缓存来更多地利用缓存。另一个答案关于使用collections.Counter 的建议可能也会加快速度。
  • 我觉得速度还可以,10个小时就可以处理2M文件。计数器方法可能更快地对 dict 值求和(正如我之前尝试过的那样),但它们往往比简单的 dicts 花费更多的内存(我不确定这是否真的如此)。
猜你喜欢
  • 2012-06-04
  • 2019-09-08
  • 2010-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-27
  • 1970-01-01
  • 2018-01-28
相关资源
最近更新 更多