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

Jam*_*mes 30 python memory

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

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

1,word1
2,word2
3,word3
Run Code Online (Sandbox Code Playgroud)

然后添加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()
Run Code Online (Sandbox Code Playgroud)

只是这样做会炸毁记忆.我查看活动监视器,它将内存固定到所有可用的高达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
Run Code Online (Sandbox Code Playgroud)

你可以看到它运行得很好.每隔几秒插入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()
Run Code Online (Sandbox Code Playgroud)

Joh*_*hin 35

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

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

看看你的代码,1行文件word1,1会创建一个dict d['1'] = 'word1'...是不是那个吵闹?

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

更新4 ...你问" 如何使用python在内存中最有效地存储键/值对 ",没有人能够准确地回答这个问题.

你有一个168 Mb的文件,650万行.那是每行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)
Run Code Online (Sandbox Code Playgroud)

注意 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
>>>
Run Code Online (Sandbox Code Playgroud)

结论: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(约为 650万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
Run Code Online (Sandbox Code Playgroud)

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

使用array.array('l')代替list(整数)值:

我们可以将这些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.

NB错误和遗漏除外 - 在我的TZ中它是0127 :-(


Ale*_*lli 20

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

>>> sys.getsizeof('word,1')
30
>>> sys.getsizeof(('word', '1'))
36
>>> sys.getsizeof(dict(word='1'))
140
Run Code Online (Sandbox Code Playgroud)

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

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

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


Fra*_*cis 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()
Run Code Online (Sandbox Code Playgroud)

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

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

  • @beagleguy:dbm风格的数据库是面向键值的,而不是SQL.修改你的程序与其中一个人合作,既然你已经在字典上工作了,不会花太多时间(相对于从MySQL到dict),然后你可以与MySQL进行比较.它总是比你的数据接管的1.3G RAM更好:) (2认同)