我在磁盘上有一个只有 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()
只是这样做会破坏内存。我查看事件监视器,它将内存与所有可用内存 Hook ,最多可达 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()
最佳答案
很多想法。但是,如果您需要实际帮助,请编辑您的问题以显示您的所有代码。还告诉我们什么是显示已使用内存的“它”,当你加载一个零条目的文件时它显示什么,你在什么平台上,什么版本的 Python。
你说“这个词可以长 1-5 个词”。 BYTES 中关键字段的平均长度是多少? id 都是整数吗?如果是这样,最小和最大整数是多少?如果不是,如果 ID 以字节为单位的平均长度是多少?要启用以上所有内容的交叉检查,您的 6.5M 行文件中有多少字节?
查看您的代码,一个 1 行文件 word1,1
将创建一个 dict d['1'] = 'word1'
......这不是bassackwards吗?
更新 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)
注意
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)
即 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 。
注意错误和遗漏除外——我的 TZ 是 0127 :-(
关于python - 在内存中加载大型字典的巨大内存使用量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2211965/