Python内存使用情况?在内存中加载大型词典

时间:2010-02-06 03:56:57

标签: python memory

嘿所有,我在磁盘上有一个只有168MB的文件。它只是一个逗号分隔的单词,id列表 这个词可以长1-5个字。有650万行。我在python中创建了一个字典,将其加载到内存中,以便我可以根据该字列表搜索传入的文本。当python将其加载到内存中时,它会显示1.3 GB的RAM空间。知道为什么会这样吗?

所以让我们说我的word文件看起来像这样......

1,word1
2,word2
3,word3
然后再增加650万 然后我遍历该文件并创建一个字典(python 2.6.1)

  def load_term_cache():
      """will load the term cache from our cached file instead of hitting mysql. If it didn't 
      preload into memory it would be 20+ million queries per process"""
      global cached_terms
      dumpfile = os.path.join(os.getenv("MY_PATH"), 'datafiles', 'baseterms.txt')
      f = open(dumpfile)
      cache = csv.reader(f)
      for term_id, term in cache:
          cached_terms[term] = term_id
      f.close()

这样做会炸毁记忆。我查看活动监视器,它将内存固定到所有可用的高达1.5GB的RAM在我的笔记本电脑上它只是开始交换。有关如何使用python在内存中最有效地存储键/值对的任何想法吗?

感谢

更新:我尝试使用anydb模块,在440万条记录之后它就死了 浮点数是自我尝试加载它以来经过的秒数

56.95
3400018
60.12
3600019
63.27
3800020
66.43
4000021
69.59
4200022
72.75
4400023
83.42
4600024
168.61
4800025
338.57

你可以看到它运行得很好。每隔几秒插入200,000行,直到我撞到墙壁并且时间加倍。

  import anydbm
  i=0
  mark=0
  starttime = time.time()
  dbfile = os.path.join(os.getenv("MY_PATH"), 'datafiles', 'baseterms')
  db = anydbm.open(dbfile, 'c')
  #load from existing baseterm file
  termfile = os.path.join(os.getenv("MY_PATH"), 'datafiles', 'baseterms.txt.LARGE')
  for line in open(termfile):
    i += 1
    pieces = line.split(',')
    db[str(pieces[1])] = str(pieces[0])
    if i > mark:
      print i
      print round(time.time() - starttime, 2)
      mark = i + 200000
  db.close()

4 个答案:

答案 0 :(得分:35)

很多想法。但是,如果您需要实际帮助,请编辑您的问题以显示所有代码。还要告诉我们显示内存使用的“it”是什么,加载零条目的文件时显示的内容,以及你所使用的平台以及Python的版本。

你说“这个词可以长到1-5个字”。 BYTES中关键字段的平均长度是多少? ids都是整数吗?如果是这样,最小和最大整数是多少?如果不是,如果ID以字节为单位,平均长度是多少?要启用上述所有内容的交叉检测,6.5M行文件中有多少字节?

查看您的代码,一行文件word1,1会创建一个字典d['1'] = 'word1' ...是不是那个贝司?

更新3:更多问题:“单词”如何编码?你确定你没有在两个领域的任何一个上携带大量的尾随空格吗?

更新4 ...您问“如何使用python 最有效地存储内存中的键/值对”和没有人回答但是没有任何准确性

你有一个拥有650万行的168 Mb文件。那是每行168 * 1.024 ** 2 / 6.5 = 27.1字节。敲掉1个字节用于逗号,1个字节用于换行符(假设它是一个* x平台),我们每行留下25个字节。假设“id”是唯一的,并且因为它看起来是一个整数,我们假设“id”是7个字节长;这使得我们的“单词”的平均大小为18个字节。这符合你的期望吗?

因此,我们希望在内存中的查找表中存储一个18字节的密钥和一个7字节的值。

让我们假设一个32位的CPython 2.6平台。

>>> K = sys.getsizeof('123456789012345678')
>>> V = sys.getsizeof('1234567')
>>> K, V
(42, 31)

请注意sys.getsizeof(str_object) => 24 + len(str_object)

一位回答者提到了元组。请注意以下事项:

>>> sys.getsizeof(())
28
>>> sys.getsizeof((1,))
32
>>> sys.getsizeof((1,2))
36
>>> sys.getsizeof((1,2,3))
40
>>> sys.getsizeof(("foo", "bar"))
36
>>> sys.getsizeof(("fooooooooooooooooooooooo", "bar"))
36
>>>

结论:sys.getsizeof(tuple_object) => 28 + 4 * len(tuple_object) ...它只允许指向每个项目的指针,它不允许项目的大小。

类似的列表分析显示sys.getsizeof(list_object) => 36 + 4 * len(list_object) ...再次需要添加项目的大小。还有一个考虑因素:CPython分配列表,这样就不必在每个list.append()调用上调用系统realloc()。对于足够大的大小(如650万!),过度分配为12.5% - 请参阅源(Objects / listobject.c)。这种分配不是用元组完成的(它们的大小不会改变)。

以下是基于内存的查找表的dict的各种替代方法的成本:

元组列表:

对于2元组本身,每个元组将占用36个字节,对于内容,每个元组将占用K和V.因此N中的N将取N *(36 + K + V);然后你需要一个列表来保存它们,所以我们需要36 + 1.125 * 4 * N.

元组列表的总数:36 + N *(40.5 + K + v)

那是26 + 113.5 * N(约709 MB ,当时为650万)

两个并行列表:

(36 + 1.125 * 4 * N + K * N)+(36 + 1.125 * 4 * N + V * N) 即72 + N *(9 + K + V)

请注意,当N为650万时,40.5 * N和9 * N之间的差异约为200MB。

存储为int而非str的值:

但这不是全部。如果ID实际上是整数,我们可以存储它们。

>>> sys.getsizeof(1234567)
12

对于每个值对象,这是12个字节而不是31个字节。当N为650万时,19 * N的差异进一步节省了大约118MB。

使用array.array('l')代替列表中的(整数)值:

我们可以将这些7位整数存储在array.array('l')中。没有int对象,也没有指向它们的指针 - 只是一个4字节的有符号整数值。额外奖励:数组仅被分配6.25%(对于大N)。所以这是1.0625 * 4 * N而不是之前的(1.125 * 4 + 12)* N,进一步节省了12.25 * N,即76 MB。

所以我们降到709 - 200 - 118 - 76 = 约315 MB

N.B。错误和遗漏除外 - 在我的TZ中它是0127 :-(

答案 1 :(得分:20)

看一下(Python 2.6,32位版本)......:

>>> sys.getsizeof('word,1')
30
>>> sys.getsizeof(('word', '1'))
36
>>> sys.getsizeof(dict(word='1'))
140

字符串(在磁盘上占用6个字节,显然)得到24字节的开销(无论多长时间,在其长度上加24以查找需要多少内存)。当你把它分成一个元组时,那就更多了。但是dict真正令人兴奋:即使是一个空的dict需要140个字节 - 纯粹的开销是维持一个超快速的基于散列的查找。为了快速,哈希表必须具有低密度 - 并且Python确保dict总是低密度(通过占用大量额外内存)。

存储键/值对的最节省内存的方法是作为元组列表,但查找当然会非常慢(即使您对列表进行排序并使用{{1}对于查找,它仍然会比dict慢得多。

考虑使用shelve代替 - 这将使用很少的内存(因为数据驻留在磁盘上)并且仍然提供相当漂亮的查找性能(当然不如内存中的字典快,但对于大量的数据将比在元组列表中查找更快更多,甚至是排序的元组,也可以! - )。

答案 2 :(得分:8)

将您的数据转换为dbm(导入anydbm,或者通过import bsddb使用berkerley db ...),然后使用dbm API访问它。

爆炸的原因是python有任何对象的额外元信息,而dict需要构造一个哈希表(这将需要更多的内存)。你刚刚创建了这么多对象(6.5M),因此元数据变得太大了。

import bsddb
a = bsddb.btopen('a.bdb') # you can also try bsddb.hashopen
for x in xrange(10500) :
  a['word%d' %x] = '%d' %x
a.close()

此代码只需1秒即可运行,所以我认为速度还可以(因为你说的是​​每秒10500行)。 btopen创建一个长度为499,712字节的db文件,hashopen创建319,488字节。

将xrange输入为6.5M并使用btopen,输出文件大小为417,080KB,完成插入约1或2分钟。所以我认为它完全适合你。

答案 3 :(得分:0)

虽然我以后有同样的问题。其他人已经很好地回答了这个问题。我提供了一个易于使用(可能不那么容易:-))和相当有效的替代方案,pandas.DataFrame。保存大数据时,它在内存使用方面表现良好。