在内存中加载大型词典的大内存使用

2024-09-23 10:34:08 发布

您现在位置:Python中文网/ 问答频道 /正文

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

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

假设我的word文件是这样的。。。

1,word1
2,word2
3,word3

再加650万。 然后我遍历该文件并创建一个字典(Python2.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()

这样做会让你的记忆破灭。我查看activity monitor,它会将内存与笔记本电脑上所有可用的高达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

你可以看到它运行得很好。每隔几秒钟就插入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()

Tags: 文件path内存idcachetimeosmy
3条回答

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

你说“这个单词可以有1-5个单词长”。密钥字段的平均长度(字节)是多少?身份证都是整数吗?如果是,那么最小和最大整数是多少?如果不是,那么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字节长;这就使得“word”的平均大小为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*(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) i、 东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')而不是list作为(整型)值:

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

所以我们的内存减少到709-200-118-76=大约315 MB

N.B.错误和遗漏除外--在我的TZ里是0127:-

看看(Python2.6,32位版本)

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

字符串(在磁盘上取6个字节,很明显)的开销为24个字节(不管它有多长,在它的长度上加上24,以确定它需要多少内存)。当你把它分成一个元组的时候,就多一点了。但是dict才是真正的爆炸点:即使是一个空的dict也需要140字节——这纯粹是维护一个非常快速的基于散列的查找take的开销。为了快速,哈希表必须具有低密度——而Python确保dict总是低密度(为此占用大量额外内存)。

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

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

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

爆发的原因是python为任何对象都有额外的元信息,dict需要构造一个散列表(这将需要更多的内存)。你刚刚创建了这么多对象(6.5百万),所以元数据变得太大了。

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分钟完成插入。所以我觉得完全适合你。

相关问题 更多 >