`namedtuple` 在内存使用上真的和元组一样高效吗?我的测试说不

Posted

技术标签:

【中文标题】`namedtuple` 在内存使用上真的和元组一样高效吗?我的测试说不【英文标题】:Is `namedtuple` really as efficient in memory usage as tuples? My test says NO 【发布时间】:2017-04-21 13:08:41 【问题描述】:

Python 文档中指出namedtuple 的优点之一是它与元组一样内存效率

为了验证这一点,我将 iPython 与 ipython_memory_usage 一起使用。测试如下图所示:

测试表明:

10000000 namedtuple 的实例使用了大约 850 MiB 的 RAM 10000000 tuple 实例在73 MiB 的 RAM 周围使用 10000000 dict 实例在570 MiB 的 RAM 周围使用

所以namedtupletuple 使用了更多 内存!甚至更多dict!!

你怎么看?我哪里做错了?

【问题讨论】:

我对您的问题没有明确的答案,但是窥视孔优化器可能注意到您的元组被定义为具有不可变成员的文字,并返回给您一个引用列表元组。 @Chinny84 -- 实际上,我真的很惊讶字典比命名元组占用更少的内存。我知道如果你在 python3.6 中工作,字典已经升级了一个新的实现,它应该更有内存效率,但我仍然认为这不应该打败一个元组...... @mgilson这可能是因为namedtuple()返回的类有一些Python级别的属性,另一方面dict仍然是纯C。 就像 mgilson 提到的,尝试动态创建元组。 CPython 可以缓存不可变对象的文字,不幸的是,namedtuple 没有文字,因此无法缓存。 @mgilson:快速检查表明您的假设是正确的。 (1, 2, 3) 的构造被常量折叠,循环中的所有 append 调用都附加相同的元组。 【参考方案1】:

更简单的衡量标准是检查等效 tuplenamedtuple 对象的大小。给定两个大致相似的对象:

from collections import namedtuple
import sys

point = namedtuple('point', 'x y z')
point1 = point(1, 2, 3)

point2 = (1, 2, 3)

获取它们在内存中的大小:

>>> sys.getsizeof(point1)
72

>>> sys.getsizeof(point2)
72

它们在我看来是一样的......


进一步复制您的结果,请注意,如果您按照自己的方式创建相同元组的列表,则每个 tuple 都是完全相同的对象:

>>> test_list = [(1,2,3) for _ in range(10000000)]
>>> test_list[0] is test_list[-1]
True

所以在你的元组列表中,每个索引都包含一个 same 对象的引用。没有 10000000 个元组,一个元组有 10000000 个引用。

另一方面,您的 namedtuple 对象列表实际上确实创建了 10000000 个唯一对象。

更好的比较是查看内存使用情况

>>> test_list = [(i, i+1, i+2) for i in range(10000000)]

和:

>>> test_list_n = [point(x=i, y=i+1, z=i+2) for i in range(10000000)]

它们的大小相同:

>>> sys.getsizeof(test_list)
81528056

>>> sys.getsizeof(test_list_n)
81528056

【讨论】:

有趣的是它的大小与字典相同:>>> test_list_d = ["x":i, "y":i+1, "z":i+2 for i in range(10000000)] >>> sys.getsizeof(test_list_d) 81528056 那是因为你总是只计算生成器对象的大小而不是生成的数据结构【参考方案2】:

自己做一些调查(使用 Python 3.6.6)。我得出以下结论:

    在所有三种情况下(元组列表、命名元组列表、字典列表)。 sys.getsizeof 返回列表的大小,无论如何它只存储引用。所以你得到 size: 81528056 在所有三种情况下。

    基本类型的大小是:

    sys.getsizeof((1,2,3)) 72

    sys.getsizeof(point(x=1, y=2, z=3)) 72

    sys.getsizeof(dict(x=1, y=2, z=3)) 240

    命名元组的计时非常糟糕: 元组列表:1.8s 命名元组列表:10s 字典列表:4.6s

    查看系统负载,我开始怀疑 getsizeof 的结果。 在测量了 Ptyhon3 进程的足迹后,我得到:

    test_list = [(i, i+1, i+2) for i in range(10000000)] 增加:1 745 564K 每个元素大约 175B

    test_list_n = [point(x=i, y=i+1, z=i+2) for i in range(10000000)] 增加:1 830 740K 每个元素大约 183B

    test_list_n = [point(x=i, y=i+1, z=i+2) for i in range(10000000)] 增加:2 717 492 K 每个元素大约 272B

【讨论】:

以上是关于`namedtuple` 在内存使用上真的和元组一样高效吗?我的测试说不的主要内容,如果未能解决你的问题,请参考以下文章

python 具名元组

Python基础语法—— 列表+元组+字典

Python namedtuple

使用 Spark 进行 RDD 和元组操作

Python3的namedtuple

详解Python列表和元组