【问题标题】:deque.popleft() and list.pop(0). Is there performance difference?deque.popleft() 和 list.pop(0)。有性能差异吗?
【发布时间】:2015-12-09 04:53:29
【问题描述】:

deque.popleft()list.pop(0) 似乎返回相同的结果。它们之间是否存在性能差异?为什么?

【问题讨论】:

    标签: python performance list deque cpython


    【解决方案1】:

    deque.popleft() 比 list.pop(0) 快,因为 deque 已被优化为在 O(1) 内执行 popleft(),而 list.pop(0) 需要 O(n)(参见deque objects)。

    _collectionsmodule.c 中用于 deque 和 listobject.c 中用于列表的注释和代码提供了实现见解以解释性能差异。即一个双端队列对象“由一个双向链表组成”,它有效地优化了两端的追加和弹出,而列表对象甚至不是单链表而是 C 数组(指向元素的指针(参见 Python 2.7 listobject.h#l22Python 3.5 listobject.h#l23),这使得它们有利于快速随机访问元素,但需要 O(n) 时间在移除第一个元素后重新定位所有元素。

    对于 Python 2.7 和 3.5,这些源代码文件的 URL 是:

    1. https://hg.python.org/cpython/file/2.7/Modules/_collectionsmodule.c

    2. https://hg.python.org/cpython/file/2.7/Objects/listobject.c

    3. https://hg.python.org/cpython/file/3.5/Modules/_collectionsmodule.c

    4. https://hg.python.org/cpython/file/3.5/Objects/listobject.c

    使用 %timeit 时,deque.popleft() 和 list.pop(0) 之间的性能差异大约是 4 倍,当 deque 和 list 都具有相同的 52 个元素时,增长到超过 1000 倍时它们的长度是 10**8。测试结果如下。

    import string
    from collections import deque
    
    %timeit d = deque(string.letters); d.popleft()
    1000000 loops, best of 3: 1.46 µs per loop
    
    %timeit d = deque(string.letters)
    1000000 loops, best of 3: 1.4 µs per loop
    
    %timeit l = list(string.letters); l.pop(0)
    1000000 loops, best of 3: 1.47 µs per loop
    
    %timeit l = list(string.letters);
    1000000 loops, best of 3: 1.22 µs per loop
    
    d = deque(range(100000000))
    
    %timeit d.popleft()
    10000000 loops, best of 3: 90.5 ns per loop
    
    l = range(100000000)
    
    %timeit l.pop(0)
    10 loops, best of 3: 93.4 ms per loop
    

    【讨论】:

      【解决方案2】:

      有性能差异吗?

      是的。 deque.popleft()O(1) -- 一个恒定时间操作。而list.pop(0)O(n) -- 线性时间运算:列表越大耗时越长。

      为什么?

      CPython 列表实现是基于数组的。 pop(0) 删除列表中的第一个项目,它需要向左移动 len(lst) - 1 项目以填补空白。

      deque() 实现使用双向链表。无论双端队列有多大,deque.popleft() 都需要恒定(以上限制)数量的操作。

      【讨论】:

      【解决方案3】:

      是的,如果你有一个很长的列表或双端队列,这是相当可观的。列表中的所有元素都连续放置在内存中,因此如果删除任何元素,所有后续元素都必须向左移动一个位置 - 因此,在列表开头删除或插入元素所需的时间与列表的长度。另一方面,双端队列被专门构造为允许在 任一 端快速插入或删除(通常通过在双端队列的开头允许“空”内存位置,或环绕以便双端队列占用的内存段的末尾可以包含实际上被认为位于双端队列开头的元素。

      比较这两个sn-ps的性能:

      d = deque([0] * 1000000)
      while d:
          d.popleft()
          if len(d) % 100 == 0:
              print(len(d))
      
      lst = [0] * 1000000
      while lst:
          lst.pop(0)
          if len(lst) % 100 == 0:
              print(len(lst))
      

      【讨论】:

      • 谢谢。所以对于列表,子序列元素必须移动。
      • 顺便说一句,deque.appendleft()list.insert(0) 也是如此
      • 斌:是的。 @AndreaCorbellini:确实如此。
      猜你喜欢
      • 2017-11-02
      • 1970-01-01
      • 1970-01-01
      • 2014-03-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-17
      • 2012-05-14
      • 2010-10-25
      相关资源
      最近更新 更多