33

我在磁盘上有一个只有 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

4 回答 4

39

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

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

查看您的代码,一个 1 行文件word1,1将创建一个 dict d['1'] = 'word1'......这不是bassackwards吗?

更新 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 * (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') 而不是 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 :-(

于 2010-02-06T04:09:05.500 回答
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 确保 adict始终是低密度的(通过为其占用大量额外内存)。

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

考虑改用shelve - 这将使用很少的内存(因为数据驻留在磁盘上)并且仍然提供相当出色的查找性能(当然不如内存中的 dict 快,但对于大量数据它将是比在元组列表上查找快得多,即使是排序的,也永远不可能!-)。

于 2010-02-06T04:07:56.730 回答
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 分钟才能完成插入。所以我认为它完全适合你。

于 2010-02-06T04:06:47.480 回答
0

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

于 2016-09-29T09:43:00.813 回答