Python 在内存中加载大型词典会占用大量内存

Python 在内存中加载大型词典会占用大量内存,python,memory,python-2.x,Python,Memory,Python 2.x,我的磁盘上有一个只有168MB的文件。这只是一个逗号分隔的单词列表,id。 单词的长度可以是1-5个字符。有650万条线路 我用python创建了一个字典,将其加载到内存中,这样我就可以根据单词列表搜索传入的文本。当python将其加载到内存中时,它显示使用了1.3 GB的RAM空间。知道为什么吗 假设我的word文件是这样的 1,word1 2、单词2 3,单词3 再加上650万。 然后循环浏览该文件并创建一个字典(python 2.6.1): 这样做会毁掉记忆。我查看activity mo

我的磁盘上有一个只有168MB的文件。这只是一个逗号分隔的单词列表,id。 单词的长度可以是1-5个字符。有650万条线路

我用python创建了一个字典,将其加载到内存中,这样我就可以根据单词列表搜索传入的文本。当python将其加载到内存中时,它显示使用了1.3 GB的RAM空间。知道为什么吗

假设我的word文件是这样的

1,word1
2、单词2
3,单词3
再加上650万。 然后循环浏览该文件并创建一个字典(python 2.6.1):

这样做会毁掉记忆。我查看activity monitor,它将内存固定在笔记本电脑上的所有可用内存上,最多可达1.5GB左右,它刚刚开始交换。你知道如何用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
你可以看到它运行得很好。每隔几秒钟插入200000行,直到我撞到墙上,时间加倍

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()

将数据转换为dbm(导入anydbm,或通过导入bsddb使用berkerley db…),然后使用DBMAPI访问它

爆发的原因是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创建一个长度为499712字节的db文件,hashopen创建319488字节

xrange输入为6.5M,使用btopen,输出文件大小为417080KB,大约需要1到2分钟才能完成插入。所以我认为它完全适合您。

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

该字符串(磁盘上占用6个字节)的开销为24个字节(无论它有多长,都要在其长度上加上24,以确定它占用了多少内存)。当你把它分解成一个元组时,这就多了一点。但是
dict
才是真正让事情变得糟糕的地方:即使是一个空的dict也需要140字节——这纯粹是维持一个非常快速的基于散列的查找所需要的开销。为了快速,哈希表必须具有低密度——Python确保
dict
始终具有低密度(通过占用大量额外内存)

存储键/值对的内存效率最高的方法是作为元组列表,但是查找当然会非常慢(即使您对列表进行排序并使用对分查找,它仍然会比dict慢得多)


考虑改用它——这将使用很少的内存(因为数据驻留在磁盘上),并且仍然提供非常快速的查找性能(当然,没有内存中的dict快,但是对于大量数据,它将比元组列表(即使是已排序的元组列表)的查找速度快得多!)。

很多想法。但是,如果需要实际帮助,请编辑问题以显示所有代码。另外,请告诉我们显示所用内存的“it”是什么,当您加载一个没有条目的文件时,它会显示什么,您所在的平台是什么,以及Python的版本是什么

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

查看您的代码,一个单行文件
word1,1
将创建一个dict
d['1']='word1'
。。。那不是巴斯克沃兹吗

更新3:更多问题:“单词”是如何编码的?您确定这两个字段中的任何一个都没有尾随空格吗

更新4。。。您问过“如何使用python在内存中最有效地存储键/值对”,但还没有人准确地回答这个问题

您有一个168MB的文件,包含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)
请注意,
sys.getsizeof(str\u对象)=>24+len(str\u对象)

一位回答者提到了元组。请仔细注意以下几点:

>>> 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\u对象)=>28+4*len(tuple\u对象)
。。。它只允许指向每个项目的指针,不允许项目的大小。

类似的列表分析显示,
sys.getsizeof(list\u对象)=>36+4*len(list\u对象)
。。。同样,有必要添加项目的大小。还有一个需要进一步考虑的问题:CPython过度分配了列表,因此它不必对每个list.append()调用调用系统realloc()。对于足够大的大小(比如650万!),超额分配为12.5%——请参阅源代码(Objects/listobject.c)。这种过度分配不是通过元组完成的(元组的大小不会改变)

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

元组列表:

对于2元组本身,每个元组将占用36个字节,对于内容,加上K和V
>>> K = sys.getsizeof('123456789012345678')
>>> V = sys.getsizeof('1234567')
>>> K, V
(42, 31)
>>> 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(1234567)
12