【问题标题】:Huge memory usage of loading large dictionaries in memory在内存中加载大字典的巨大内存使用
【发布时间】:2011-01-13 19:03:49
【问题描述】:

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

【问题讨论】:

    标签: python memory python-2.x


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

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

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

      查看您的代码,一个 1 行文件 word1,1 将创建一个字典 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:

      但这还不是全部。如果 IDs 实际上是整数,我们可以这样存储它们。

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

      注意错误和遗漏除外——在我的 TZ 中是 0127 :-(

      【讨论】:

      • 嘿,约翰,好的,我进行了编辑。希望这有助于表明我正在做的事情很愚蠢:)
      • doh,是的,我写错了,我编辑它以显示数据如何存储在磁盘 1,word
      • 是的,在使用 wordstr.strip() 保存之前,它都被剥离了
      • @beagleguy:请发布您实际运行的代码,即更喜欢复制/粘贴。并回答其他问题。
      • 该代码是实际运行以加载术语文件的代码
      【解决方案3】:

      将你的数据转换成dbm(导入anydbm,或者使用berkerley db by import bsddb ...),然后使用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 分钟才能完成插入。所以我觉得它完全适合你。

      【讨论】:

      • 我从数据库移到内存中的原因是我把它放在一个 mysql 表中,单词作为主键(只能有一个唯一的单词或短语)。我正在执行大约 2000 万次查询来处理一小时的数据。将其移至内存可节省大约 10 分钟的处理时间。你有什么想法可以帮助解决这种情况吗?
      • 您不需要 SQL 数据库(它们功能强大但速度较慢)。 DBM 是哈希值风格的数据库,因此它们应该很快(非常类似于 dict 对象)并且需要比 SQL 更小的内存占用。 Berkerley db 将是我的最佳选择(导入 bsddb)。
      • @beagleguy:dbm 风格的数据库是面向键值的,而不是 SQL。修改您的程序以使用其中一个,现在您已经在字典上工作,不会花费太多时间(与从 MySQL 到 dict 相比),然后您可以与 MySQL 进行比较。它总是比你的数据占用 1.3G 的 RAM 更好:)
      • 嗨,Heim,我今晚实际上尝试使用 Redis 来实现这个目的。插入数据需要 10 分钟。我平均每秒插入 10,500 次,比 redis 基准测试少 10 倍。我正在使用 python redis 客户端模块。对于同一个 168MB 的文件,redis 也占用了大约 1GB 的内存。
      • 好吧,我想你不必担心 3.0,除非你非常有决心“现在”使用 3.0。 shelve 更像是一个 python 原生存储,所以它的工作原理应该类似,但性能上没有任何保证。
      【解决方案4】:

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

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

      字符串(显然在磁盘上占用 6 个字节)会产生 24 个字节的开销(不管它有多长,将其长度加 24 以找出它占用了多少内存)。当你把它拆分成一个元组时,它会多一点。但是dict 才是真正让事情变得糟糕的地方:即使是一个空的字典也需要 140 个字节——纯粹是维护一个基于散列的快速查找的开销。为了速度快,哈希表必须具有低密度——Python 确保 dict 始终是低密度的(为此占用大量额外内存)。

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

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

      【讨论】:

      • 感谢 alex 提供的一些重要信息,我现在正在尝试搁置,并会告诉你进展如何
      • hrm shelve 是不行的,它在 500 万行后开始死亡,我在 20 分钟后杀死了插入
      • @beagleguy:你试过用 protocol=-1 搁置吗?
      • protocol=-1 是一个很好的建议,但我不知道“开始死亡”是什么意思——可能只是在耗尽物理内存后导致操作缓慢的垃圾行为。所以接下来要尝试的是一个真正的数据库,无论是否关系——bsddb 是一个(非关系),并被 anydbm 隐式使用(并且,有一个间接,shelve),但版本Python 附带的不是最好的或最快的。首先,我会尝试使用 Python 附带的sqlite,以避免安装任何其他部件;如果还不够,接下来是第三方数据库。
      猜你喜欢
      • 2016-09-05
      • 1970-01-01
      • 1970-01-01
      • 2014-11-18
      • 1970-01-01
      • 2013-05-15
      • 1970-01-01
      • 2012-12-30
      • 1970-01-01
      相关资源
      最近更新 更多