奇怪的Python数据结构内存占用情况

时间:2018-07-31 18:23:54

标签: python memory-management heap puzzle programming-pearls

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

  

给出一个最多包含一千万个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条记录

生成的树是

Generated Tree

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

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

,并且在8999999行处也提供了368个字节

我建立了一个名为xtree的辅助地图,该地图仅用于提供数据

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

Data analysis

任何人都可以解释一下这是怎么回事吗??

1 个答案:

答案 0 :(得分:4)

您的tree只是一个最多有10个键值对的字典。在更大的树中,不再有键/值对。键值对中…内的值中还有更多值,但是dict中仍然只有10个键值对。大约有10个键值对占用368个字节的字典看起来像您应该期望的那样。 1

getsizeof的文档说:

  

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

...

  

有关recursive sizeof recipe递归使用getsizeof()来查找容器的大小及其所有内容的示例。

由于您实际上并不具有完全任意的数据结构,而只是一个dict等等的字典。而且,当您 do 时有一些共享的引用(例如,如果您阅读数字{ {1}}当您已经有一个具有相同内存值的int时,Python只会重用相同的对象),如果您试图验证自己是否适合1.5MB,则确实需要进行最坏情况的测量,因此您可能要跳过对已经看到的值的检查。

因此,您可以根据需要编写更简单的方法,而不使用该配方。但是想法是一样的:

1234567

另一方面,您的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 是具有8999999键值对的字典。进行相同的后端计算,我希望它不到300MB。相反,它超过了300MB。足够靠近。

您还将在堆中存储8999999 7位整数。为了获得一些不错的整数,我们假设有5M个不同的整数,它们不属于CPython预先创建和缓存的少数小值。这些整数中的每一个都足够小以适合一个30位数字,因此在64位CPython上它们每个占用28个字节。因此,如果您在任一{{1上调用了上面的递归函数,请在上面调用递归函数,这是另外xtree中未占的140MB(但它们占了-实际上,占了过多的空间,给出了最坏情况的测量实现)。 }}或sys.getsizeof(xtree)

因此,您在treextree之间的总内存使用量和实际整数可能约为750MB,这不太符合tree的要求。 / p>


1。每个Python对象都有一些固定的标头开销,例如refcount,指向类型的指针等,以及特定于类型的事物(例如大多数容器类型的长度)。调用该64个字节。然后,字典有一个哈希表。它必须大于10个插槽,以将负载保持在1.0以下。称之为13个插槽。每个插槽都需要一个散列值,一个对键的引用以及对该值的引用,因此为3个指针或24个字节。 64 + 13 * 24 =376。因此,封底计算仅减少了8个字节…