您观察到的性能差是由您所使用的版本中的Python垃圾收集器中的错误引起的。升级到Python 2.7或3.1或更高版本,以重新获得Python中列表追加所期望的摊销0(1)行为。
如果无法升级,请在生成列表时禁用垃圾收集,并在完成后将其打开。
(您还可以调整垃圾回收器的触发器,或者在进行过程中有选择地调用collect,但我不会在这个答案中探索这些选项,因为它们更复杂,我怀疑您的用例可以接受上述解决方案。)
背景:
报告者观察到,随着列表长度的增长,将复杂对象(不是数字或字符串的对象)附加到列表的速度会线性减慢。
此行为的原因是垃圾收集器正在检查并重新检查列表中的每个对象,以查看它们是否符合垃圾收集的条件。此行为导致向列表中添加对象的时间线性增加。修复程序预计会在py3k中落地,因此它不应该应用于您正在使用的解释器。
测试:
我做了个测试来证明这一点。对于1k迭代,我将10k对象追加到列表中,并记录每次迭代的运行时。整体运行时差异是显而易见的。在测试的内部循环中禁用了垃圾收集,我的系统运行时为18.6秒。在整个测试中启用了垃圾收集,运行时为899.4秒
这是测试:import time
import gc
class A:
def __init__(self):
self.x = 1
self.y = 2
self.why = 'no reason'
def time_to_append(size, append_list, item_gen):
t0 = time.time()
for i in xrange(0, size):
append_list.append(item_gen())
return time.time() - t0
def test():
x = []
count = 10000
for i in xrange(0,1000):
print len(x), time_to_append(count, x, lambda: A())
def test_nogc():
x = []
count = 10000
for i in xrange(0,1000):
gc.disable()
print len(x), time_to_append(count, x, lambda: A())
gc.enable()
图形结果:红色表示gc打开,蓝色表示gc关闭。y轴按秒对数缩放。
由于这两个图在y分量上相差几个数量级,因此它们在y轴线性缩放的情况下是独立的。
有趣的是,在垃圾收集关闭的情况下,我们看到每10k个appends的运行时只有小的峰值,这表明Python的列表重新分配成本相对较低。无论如何,它们比垃圾收集成本低很多数量级。
上面这些图的密度使我们很难看出,在启用垃圾收集器的情况下,大多数间隔实际上都有很好的性能;只有当垃圾收集器循环时,我们才会遇到这种病态行为。你可以在10公里附加时间的直方图中观察到这一点。大多数数据点在每10公里附件中下降0.02秒左右。