我对set
s:
>>> 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
那么为什么range
和list
不会发生这种情况,而只会set
?
这背后有任何设计决定,还是一个错误?
在Linux 64位上测试python 2.7.3和python 3.2.3。
答案 0 :(得分:9)
在Python 2.7.3中,set.union()
委托给名为set_update_internal()
的C函数。后者根据其参数的Python类型使用几种不同的实现。这种多样化的实现解释了您所进行的测试之间的行为差异。
当参数为set
时使用的实现在代码中记录了以下假设:
/* 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
的元素。
如果分配是一个交易破坏者,有很多方法可以解决它,其中一些你已经发现。