python - 如果参数是一个集合,为什么 union 会消耗更多内存?

标签 python memory-management set

我对 set 的内存分配行为感到困惑:

>>> set(range(1000)).__sizeof__()
32968
>>> set(range(1000)).union(range(1000)).__sizeof__()       # expected, set doesn't change
32968
>>> set(range(1000)).union(list(range(1000))).__sizeof__() #expected, set doesn't change
32968
>>> set(range(1000)).union(set(range(1000))).__sizeof__()  # not expected
65736

为什么使用set 作为参数加倍结果set 使用的内存量? 两种情况下的结果都与原始 set 相同:

>>> set(range(1000)) == set(range(1000)).union(range(1000)) == set(range(1000)).union(set(range(1000)))
True

请注意,使用普通迭代器也会发生同样的情况:

>>> set(range(1000)).union(iter(list(range(1000)))).__sizeof__()
32968

并使用 update 方法:

>>> a.update(range(1000))
>>> a.__sizeof__()
32968
>>> a.update(set(range(1000)))
>>> a.__sizeof__()
65736

起初我以为是因为调用union时,它看到另一个set的大小是1000,因此决定分配足够的内存来容纳两个 set 的所有元素,但之后它只使用该内存的一部分,而在迭代器的情况下,它只是对其进行迭代并一个一个地添加元素(这不会消耗更多内存,因为所有元素都已在 set 中)。

但是 range 也是一个序列,第一个示例中的 list 也是一个序列。

>>> len(range(1000))
1000
>>> range(1000)[100]
100

那么,为什么这不会发生在 rangelist 上,而只会发生在 set 上? 这背后是否有任何设计决策或它是一个错误?


在 64 位 linux 上的 python 2.7.3 和 python 3.2.3 上测试。

最佳答案

在 Python 2.7.3 中,set.union()委托(delegate)给一个名为 set_update_internal() 的 C 函数.后者根据其参数的 Python 类型使用几种不同的实现。这种实现的多样性解释了您进行的测试之间的行为差​​异。

当参数是一个集合时使用的实现在代码中记录了以下假设:

/* Do one big resize at the start, rather than
 * incrementally resizing as we insert new keys.  Expect
 * that there will be no (or few) overlapping keys.
 */

显然,在您的特定情况下,没有(或很少)重叠键的假设是不正确的。这就是导致最终 set 过度分配内存的原因。

不过,我不确定我是否会将其称为错误。 set 的实现者选择了在我看来合理的权衡,而您只是发现自己站在了权衡的错误一边。

权衡的好处是,在许多情况下,预分配会带来更好的性能:

In [20]: rhs = list(range(1000))

In [21]: %timeit set().union(rhs)
10000 loops, best of 3: 30 us per loop

In [22]: rhs = set(range(1000))

In [23]: %timeit set().union(rhs)
100000 loops, best of 3: 14 us per loop

在这里,set 版本的速度是原来的两倍,大概是因为它在从 rhs 添加元素时不会重复重新分配内存。

如果过度分配会破坏交易,有多种方法可以解决它,其中一些您已经发现。

关于python - 如果参数是一个集合,为什么 union 会消耗更多内存?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15198042/

相关文章:

python - 对字典列表应用集合操作

python - 用于 Python 脚本的可移植 exe 包装器。在什么情况下我的解决方案不起作用?

c++ - 检查数据库中的指纹

delphi - Delphi/DBExpress 的内存泄漏

java - 如何在对象中存储 Set/HashSet?

data-structures - 是否有联合和相交 Haskell Prelude 实现?

python - python 多重继承中的 super() 用法

python - 在保留 matplotlib 样式的同时使用 seaborn 缩放图形的字体

Java:填充内存中排序的批处理

perl - 如何在 Solaris 上的 Perl 中监视内存使用情况?