Python数据结构内存占用表现奇怪

Posted

技术标签:

【中文标题】Python数据结构内存占用表现奇怪【英文标题】:Python Data Structure memory footprint behaving weird 【发布时间】:2019-01-08 05:30:03 【问题描述】:

我正在尝试其中一颗编程珍珠:

给定一个最多包含一千万个没有重复的 7 位整数的文件。仅使用 1.5Mb RAM 并仅读取一次数据以升序打印这些数字的有效方法是什么?只有 1Mb 的 RAM 而没有其他存储的后果是什么?如果允许重复,你的答案会有什么变化?

为了创建测试用例 I,生成了 8999999 个数字并将它们写入文件。 然后对于每一行,我开始将相同的插入到树中,最后创建一个 trie 结构。

示例代码:

from sys import getsizeof

tree = dict()
xtree = dict()
f = open("data2.txt", "r")
cnt = 0
for number in f:
    cnt += 1
    currTree = tree
    xtree[number] = dict()
    for n in number.strip():
        if n not in currTree:
            currTree[n] = dict()
        currTree = currTree[n]
f.close()

print(cnt)
print(getsizeof(tree))
print(getsizeof(xtree))
print(tree)

示例文件 data2.txt 有 20 条记录

生成的树是

现在的问题是,当我对构建的树进行内存大小调整时,它会在 20 行处显示 240 字节的内存占用空间

在 100 行时,树的大小变为 368 字节

在 8999999 行也提供 368 个字节

我构建了一个名为 xtree 的辅助地图,它只是输入数据

xtree 和 tree 的大小以字节为单位。

谁能解释一下这是怎么回事??

【问题讨论】:

从字典实现的树使用太多内存。你想要的是一个整数,如果i 在列表中,则在其中设置ith 位。 10,000,000 位仅略低于 1.2Mb。 @chepner 请不要破坏提问者(以及碰巧阅读您评论的任何人)的难题。他们没有要求解决这个难题。 @jbch 这不是 puzzling.stackexchange.com。 【参考方案1】:

您的 tree 只是一个包含最多 10 个键值对的字典。在更大的树中,没有更多的键值对。键值对中的 ... 中的值内有更多值,但字典中仍然只有 10 个键值对。一个包含大约 10 个键值对、占用 368 个字节的 dict 似乎符合您的预期。1

正如getsizeof 的文档所说:

只考虑直接归因于对象的内存消耗,而不考虑它所引用的对象的内存消耗。

请参阅recursive sizeof recipe,了解使用getsizeof() 递归查找容器大小及其所有内容的示例。

因为您实际上并没有完全任意的数据结构,而只是一个 dict 等的 dict。而且,虽然您 确实 有一些共享引用(例如,如果您阅读数字 @ 987654326@ 虽然你已经在内存中有一个具有相同值的 int,但 Python 只会重用相同的对象),如果你试图验证你是否可以放入 1.5MB,你真的想要一个最坏情况的测量,所以你可能想跳过检查已经看到的值。

所以,如果你愿意,你可以写一些更简单的东西而不是使用那个食谱。但想法是一样的:

def total_dict_size(d):
    size = sys.getsizeof(d)
    if isinstance(d, dict):
        for key, value in d.items():
            size += sys.getsizeof(key) + total_dict_size(value)
    return size

另一方面,您的 xtree 是一个包含 8999999 个键值对的字典。做同样的粗略计算,我希望它会低于 300MB。相反,它有点超过 300MB。足够接近了。

您还将 8999999 个 7 位整数存储在堆上。取一些漂亮的整数,假设有 5M 不同的整数不属于 CPython 预先创建和缓存的少数小值。这些整数中的每一个都小到可以放入一个 30 位数字,因此在 64 位 CPython 上它们每个占用 28 个字节。因此,如果您在 tree 或 @ 上调用上面的递归函数,那是 sys.getsizeof(xtree) 中没有考虑到的另外 140MB(但它们被考虑了——事实上,被过度考虑了,给出了最坏情况的测量实现) 987654331@.

因此,treextree 和实际整数之间的总内存使用量可能在 750MB 左右,这不太符合 < 1.5MB 的要求。


1。每个 Python 对象都有一些固定的标头开销,例如引用计数、指向类型的指针等,以及特定于类型的东西,例如大多数容器类型的长度。称之为 64 字节。一个 dict 然后有一个哈希表。它需要比 10 个插槽大一点,以保持负载远低于 1.0;称之为 13 个插槽。每个插槽都需要一个哈希值、一个对键的引用和一个对值的引用,所以这是 3 个指针,或 24 个字节。 64 + 13 * 24 = 376。所以粗略计算只减少了 8 个字节……

【讨论】:

以上是关于Python数据结构内存占用表现奇怪的主要内容,如果未能解决你的问题,请参考以下文章

『Python』内存分析_List对象内存占用分析

python 内存占用分析工具

python 进程内存增长问题, 解决方法和工具

Python整数在循环中表现得很奇怪

为啥 CUDA 内存复制速度表现得像这样,一些恒定的驱动程序开销?

如何优化Python占用的内存,面试必学