为什么元组在评估生成器自身时比列表慢?

Why are tuples slower than lists at evaluating generators into themselves?

python -m timeit "tuple(xrange(600000))"

100 loops, best of 3: 11.5 msec per loop

python -m timeit "list(xrange(600000))"

100 loops, best of 3: 10.1 msec per loop


将它们与 dis 模块进行比较:

>>> from dis import dis
>>> dis(lambda: tuple(xrange(600000)))
      0 LOAD_GLOBAL              0 (tuple)
      3 LOAD_GLOBAL              1 (xrange)
      6 LOAD_CONST               1 (600000)
      9 CALL_FUNCTION            1
     12 CALL_FUNCTION            1
     15 RETURN_VALUE

>>> dis(lambda: list(xrange(600000)))
      0 LOAD_GLOBAL              0 (list)
      3 LOAD_GLOBAL              1 (xrange)
      6 LOAD_CONST               1 (600000)
      9 CALL_FUNCTION            1
     12 CALL_FUNCTION            1
     15 RETURN_VALUE

我猜这在很大程度上取决于您的运行时间。例如。在cpython中,list和tuple函数都是用C语言编写的,由于list的使用非常频繁,很可能list方法比tuple方法有更多的优化。

其他实现的行为可能不同。

由于迭代器通常不会预先指定大小,因此元组和列表都需要使用过度分配策略来处理任意大小的可迭代对象。就目前而言,xrange() 对象确实有一个 __len__ 方法,元组和列表使用的 _PyObject_LengthHint() function used 将利用它一次设置正确的目标大小。所以在这种情况下,list() 代码只是稍微更有效,因为它内联迭代以避免 NULL 比较。

跟着我看代码;在这种情况下,唯一真正的区别是迭代器的展开方式和值的复制方式。因为 list()tuple() 对象跟踪不同的信息,所以这些循环在实现上略有不同。参见:

tuple() 代码路径使用 PyIter_Next(),而 list() 代码路径内联不必测试 NULL 两次。除此之外,循环做相同数量的工作。我认为这是 NULL 测试,放大超过 600000 次迭代,解释了这里的时间差异。

不管怎么说,你发现的时差真的没有那么大;在我的机器上重复运行将时间差异保持在 10% 以内(每次 list 获胜)。时间差随着使用的xrange()大小线性增加。