Python 内存消耗:dict VS 元组列表
Posted
技术标签:
【中文标题】Python 内存消耗:dict VS 元组列表【英文标题】:Python memory consumption: dict VS list of tuples 【发布时间】:2013-03-16 12:01:02 【问题描述】:有很多关于不同 python 数据类型的内存消耗的问题和讨论。然而,他们中很少有人(如果有的话)遇到非常具体的情况。当你想在内存中存储大量的键值数据时,哪种数据结构更节省内存,字典还是元组列表?
一开始我认为 dict 比元组列表更强大,而且这种能力必须付出一些代价,实际上空 dict 确实比空列表或元组占用更多内存(请参阅In-memory size of a Python structure),所以我想使用[(key1, value1), (key2, value2), ...]
会比 key1: value1, key2: value2, ...
更节省内存。
看来我错了。只需启动以下代码 sn-p,然后查看您的操作系统报告的内存消耗。我正在使用 Windows XP,因此任务管理器告诉我,一个大字典“只”吃掉了 40MB 内存和 40MB 虚拟内存,但元组列表吃掉了 60MB 内存和 60MB 虚拟内存。
怎么可能?
from sys import getsizeof as g
raw_input('ready, press ENTER')
i = 1000000
#p = [(x, x) for x in xrange(i)] # Will print 4,348,736 40,348,736
p = dict((x, x) for x in xrange(i)) # Will print 25,165,964 37,165,964
print g(p), g(p) + sum(g(x) for x in p)
raw_input("Check your process's memory consumption now, press ENTER to exit")
更新:
感谢下面的一些 cmets。我想澄清一下:我说的是内存效率。不,在这种情况下无需担心键值查找效率,让我们假设我的算法将通过迭代器一一消耗它们。
【问题讨论】:
你问错问题了。如果您需要键值查找,请使用 dict。如果您需要一个数组,请使用列表或元组。 Python 为字典保留一个哈希表。 This link 来自 another answer 我认为,字典的查找速度更快,并且元组使用的内存更少。 对于某些类型的数据,您可以使用比您的两个选项更优化的数据,例如 trie。 对什么有效?为了有效地使用内存或进行快速查找? 【参考方案1】:tuple
s 中的 list
添加了一个额外的层。您有 3 层项目:
而您的 dict
仅适用:
正是这 100 万个元组加上保存对它们的引用的列表比 100 万个缓存散列占用更多的内存。这里涉及的指针多出大约 50%,很容易占到您看到的多出 50% 的内存使用量。
您的元组列表还有一个缺点:查找时间。要在 dict 中找到匹配的键,需要 O(1) 复杂度成本。要在元组列表中执行相同操作,您必须潜在地扫描整个列表以获得 O(n) 成本。如果您需要将键映射到值,请不要使用元组列表。
【讨论】:
我认为你对额外层的看法是正确的。那么您认为在这种情况下,即使我只想“保存”这些数据,dict 仍然是最节省内存的吗? (假设我不需要随机查找。) @Iceberg:我不会持有数据。如果您不查找其中的内容,那有什么意义?你也可以使用 flat 元组,所以没有嵌套对;您仍然可以轻松地重新创建配对。 我想要的是持有它们然后迭代它们。扁平元组技巧可能会有所帮助,但代价是失去可读性。 @Iceberg:成对迭代:Iterating over every two elements in a list【参考方案2】:在这种情况下,您实际上得到的内存使用情况并不完整。字典的总大小不规则地增加一倍以上,如果在字典大小增加后立即比较这两个结构的大小,它会再次变大。一个带有递归大小函数的简单脚本(见下面的代码)显示了一个非常清晰的模式:
i: 2 list size: 296 dict size: 328 difference: -32
i: 3 list size: 392 dict size: 352 difference: 40
i: 4 list size: 488 dict size: 376 difference: 112
i: 5 list size: 616 dict size: 400 difference: 216
i: 7 list size: 808 dict size: 1216 difference: -408
i: 10 list size: 1160 dict size: 1288 difference: -128
i: 13 list size: 1448 dict size: 1360 difference: 88
i: 17 list size: 1904 dict size: 1456 difference: 448
i: 23 list size: 2480 dict size: 3904 difference: -1424
i: 31 list size: 3328 dict size: 4096 difference: -768
i: 42 list size: 4472 dict size: 4360 difference: 112
i: 56 list size: 5912 dict size: 4696 difference: 1216
i: 74 list size: 7880 dict size: 5128 difference: 2752
i: 100 list size: 10520 dict size: 14968 difference: -4448
i: 133 list size: 14024 dict size: 15760 difference: -1736
i: 177 list size: 18672 dict size: 16816 difference: 1856
随着i
的增长,这种模式仍在继续。 (您可以使用您的方法对此进行测试——尝试将i
设置在2636744
附近。此时字典的大小更大,至少对我而言。)Martijn 是正确的,元组列表中的元组增加内存开销,抵消了列表相对于字典的内存优势。但平均而言,结果并不是字典更好,而是字典更好。就是字典差不多。所以回答你原来的问题:
当您想在内存中存储大量键值数据时,哪种数据结构更节省内存,是字典还是元组列表?
如果您只关心内存,这并不重要。
然而,请注意迭代字典通常比迭代列表要慢一些,因为没有避免迭代字典中所有空箱的好方法。所以有一点折衷——字典在进行随机键查找时(快得多),但列表在迭代时(有点)快。大多数时候字典可能会更好,但在极少数情况下,列表可能会提供微优化。
这是测试大小的代码。它可能不会为所有极端情况生成正确的结果,但它应该可以毫无问题地处理像这样的简单结构。 (但如果您发现任何问题,请告诉我。)
import sys, collections, itertools, math
def totalsize(x):
seen = set()
return ts_rec(x, seen)
def ts_rec(x, seen):
if id(x) in seen:
return 0
else:
seen.add(id(x))
x_size = sys.getsizeof(x)
if isinstance(x, collections.Mapping):
kv_chain = itertools.chain.from_iterable(x.iteritems())
return x_size + sum(ts_rec(i, seen) for i in kv_chain)
elif isinstance(x, collections.Sequence):
return x_size + sum(ts_rec(i, seen) for i in x)
else:
return x_size
for i in (10 ** (e / 8.0) for e in range(3, 19)):
i = int(i)
lsize = totalsize([(x, x) for x in xrange(i)])
dsize = totalsize(dict((x, x) for x in xrange(i)))
print "i: ", i,
print " list size: ", lsize, " dict size: ", dsize,
print " difference: ", lsize - dsize
【讨论】:
感谢您的帮助。至少您比对我的问题发表评论的人更了解原始问题。所以我对你的理解是“这取决于......要持有多少物品,如果长度已知且固定,我们最好有一个基准”。顺便说一句,我将您的脚本修改为for i in (10 ** (e /8.0) for e in range(3, 49)):
,并且当 i=42169、56234、74989 ……一直到 i=1000000 时,dict 总是优于元组列表。哦,谢谢你也提到了迭代速度。
@Iceberg,是的,这就是我的意思。但我要补充一点,除非你正在做一些认真的微优化,否则基准测试并不值得麻烦。使用对您的问题有实际意义的结构。另一方面,如果您正在进行微优化并且不关心随机密钥访问,那么您可能会从 Martijn 建议的平面列表中获得最佳结果。
当说“微优化”时,人们可能会暗示努力不值得?但在这种面向内存的情况下,正负差的范围可以从 2% 到 71%。它是意义重大!此外, dict 在语义上类似于元组列表,但与平面列表不同。总而言之,现在我们知道了所有的利弊,所以当我们认为它适合某种情况时,我们可以选择其中任何一个。感谢所有为这个帖子做出贡献的人!以上是关于Python 内存消耗:dict VS 元组列表的主要内容,如果未能解决你的问题,请参考以下文章