【问题标题】:Why are tuples slower than lists at evaluating generators into themselves?为什么元组在评估生成器时比列表慢?
【发布时间】:2015-03-18 16:52:04
【问题描述】:
python -m timeit "tuple(xrange(600000))"

100 次循环,3 次中的最佳:每个循环 11.5 毫秒

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

100 个循环,3 个循环中的最佳:每个循环 10.1 毫秒


将它们与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

【问题讨论】:

  • 这相差太小了,真的。但是列表是动态数组,元组是固定大小的,所以后者首先需要一个精确的大小。

标签: python performance list tuples python-internals


【解决方案1】:

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

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

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

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

【讨论】:

  • 当传递的对象不是列表或元组时,额外的PyObject_LengthHint 调用也会出现在listextend 中,尽管两者都将其用于不同的目的。
  • @AshwiniChaudhary:是的,但是元组分配的步骤更大,调整大小实际上必须消除过度分配。
  • @AshwiniChaudhary:完全重写,我想我在这里找到了问题。
【解决方案2】:

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

其他实现可能会有不同的表现。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-08-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多