Python deque.popleft()和list.pop(0)。有性能差异吗?

Python deque.popleft()和list.pop(0)。有性能差异吗?,python,performance,list,deque,cpython,Python,Performance,List,Deque,Cpython,deque.popleft()和list.pop(0)似乎返回相同的结果。它们之间是否存在性能差异?原因是什么?是的,如果您有一个很长的列表或deque,这是相当可观的。列表中的所有元素都是连续放置在内存中的,因此如果删除任何元素,所有后续元素都必须向左移动一个位置-因此,在列表开头删除或插入元素所需的时间与列表的长度成正比。另一方面,deque专门构造为允许在任意一端快速插入或移除(通常通过允许deque开头的“空”内存位置,或环绕,以便deque占用的内存段末尾可以包含实际被认为在deque

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

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

比较这两个代码段的性能:

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.popleft()
O(1)
——一种恒定时间操作。而
list.pop(0)
O(n)
——线性时间运算:列表越大,所需时间越长

为什么?

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

deque()
实现使用双链接列表。无论deque有多大,
deque.popleft()
都需要一个常量(限制在上面)的操作数。

deque.popleft()比list.pop(0)快,因为deque经过优化,大约在O(1)中执行popleft(),而list.pop(0)需要O(n)(请参阅)

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

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

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

    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
    

    谢谢。所以对于列表,子任务元素必须移位。顺便说一句,对于
    deque.appendleft()
    vs
    list.insert(0)
    Bin:Yes.@AndreaCorbellini:是的。@Bin:我只提到了CPython,因为我在中找不到合适的引用(如果Python引用没有指定Python列表的时间复杂度,这看起来像是一个bug)。尽管在实践中,所有Python实现(据我所知)都尊重预期的时间复杂度。关于pop(0)和insert(0,v)列表的时间复杂度的评论。@TrisNefzger:deque docs express“常识”但是在传递中指定列表行为似乎没有语言引用那么具有约束力。你完全正确。应该有一个官方的、有约束力的文档,介绍类似Python集合的数据结构的性能特征,类似于for Scala。