python:deque vs list性能比较

时间:2014-05-06 06:23:24

标签: python performance data-structures benchmarking deque

在python文档中,我可以看到deque是一个特别的集合,经过高度优化,可以从左侧或右侧弹出/添加项目。例如。文件说:

  

Deques是堆栈和队列的概括(名称是   发音为“deck”,是“双端队列”的缩写。双端   支持线程安全,内存高效的附加和弹出   deque的一侧具有大致相同的O(1)性能   任何方向。

     

虽然列表对象支持类似的操作,但它们已经过优化   快速固定长度操作并导致O(n)内存移动成本   pop(0)和insert(0,v)操作,它们改变了大小和   基础数据表示的位置。

我决定使用 ipython 进行一些比较。谁能解释一下我在这里做错了什么:

In [31]: %timeit range(1, 10000).pop(0)
 10000 loops, best of 3: 114 us per loop

In [32]: %timeit deque(xrange(1, 10000)).pop() 
10000 loops, best of 3: 181 us per loop

In [33]: %timeit deque(range(1, 10000)).pop()
1000 loops, best of 3: 243 us per loop

4 个答案:

答案 0 :(得分:66)

Could anyone explain me what I did wrong here

是的,您的时间由创建列表或双端队列的时间决定。相比之下, pop 的时间是微不足道的。

相反,您应该从设置时间中隔离您尝试测试的内容(弹出速度):

In [1]: from collections import deque

In [2]: s = range(1000)

In [3]: d = deque(s)

In [4]: s_append, s_pop = s.append, s.pop

In [5]: d_append, d_pop = d.append, d.pop

In [6]: %timeit s_pop(); s_append(None)
10000000 loops, best of 3: 115 ns per loop

In [7]: %timeit d_pop(); d_append(None)
10000000 loops, best of 3: 70.5 ns per loop

尽管如此,deques和list在性能方面的真正差异是:

  • 对于 appendleft() popleft(),Deques的速度为O(1),而列表具有插入的O(n)性能(0) ,值) pop(0)

  • 列表追加性能受到影响,因为它使用了 realloc()。因此,它往往在简单代码中具有过度乐观的时序(因为realloc不必移动数据)并且实际代码中的时间确实很慢(因为碎片强制重新分配以移动所有数据)。相反,deque追加性能是一致的,因为它永远不会重新分配并且永远不会移动数据。

答案 1 :(得分:17)

值得的:

> python -mtimeit -s 'import collections' -s 'c = collections.deque(xrange(1, 100000000))' 'c.pop()'
10000000 loops, best of 3: 0.11 usec per loop

> python -mtimeit -s 'c = range(1, 100000000)' 'c.pop()'
10000000 loops, best of 3: 0.174 usec per loop

> python -mtimeit -s 'import collections' -s 'c = collections.deque()' 'c.appendleft(1)'
10000000 loops, best of 3: 0.116 usec per loop

> python -mtimeit -s 'c = []' 'c.insert(0, 1)'
100000 loops, best of 3: 36.4 usec per loop

正如您所看到的,appendleftinsert相比,它真正闪耀的地方。

答案 2 :(得分:0)

我建议您参考 https://wiki.python.org/moin/TimeComplexity

Python列表和双端队列在大多数操作(推,弹出等)中具有相似的复杂性

答案 3 :(得分:0)

我找到了解决此问题的方法,并认为我会提供一个带有一些上下文的示例。
使用Deque的经典用例可能是旋转/移动集合中的元素,因为(如其他人所提到的),两端的push / pop操作都具有非常好的(O(1))复杂度,因为这些操作只是移动引用而不是列表,列表必须物理地在内存中移动对象。

因此,这里有两种非常个向左旋转功能的实现:

def rotate_with_list(items, n):
    l = list(items)
    for _ in range(n):
        l.append(l.pop(0))
    return l

from collections import deque
def rotate_with_deque(items, n):
    d = deque(items)
    for _ in range(n):
        d.append(d.popleft())
    return d
  

注意:这是双端队列的一种常见用法,该双端队列具有内置的rotate方法,但是为了直观比较,我在这里手动进行此操作。

现在让我们%timeit开始。

In [1]: def rotate_with_list(items, n):
   ...:     l = list(items)
   ...:     for _ in range(n):
   ...:         l.append(l.pop(0))
   ...:     return l
   ...: 
   ...: from collections import deque
   ...: def rotate_with_deque(items, n):
   ...:     d = deque(items)
   ...:     for _ in range(n):
   ...:         d.append(d.popleft())
   ...:     return d
   ...: 

In [2]: items = range(100000)

In [3]: %timeit rotate_with_list(items, 800)
100 loops, best of 3: 17.8 ms per loop

In [4]: %timeit rotate_with_deque(items, 800)
The slowest run took 5.89 times longer than the fastest. This could mean that an intermediate result is being cached.
1000 loops, best of 3: 527 µs per loop

In [5]: %timeit rotate_with_list(items, 8000)
10 loops, best of 3: 174 ms per loop

In [6]: %timeit rotate_with_deque(items, 8000)
The slowest run took 8.99 times longer than the fastest. This could mean that an intermediate result is being cached.
1000 loops, best of 3: 1.1 ms per loop

In [7]: more_items = range(10000000)

In [8]: %timeit rotate_with_list(more_items, 800)
1 loop, best of 3: 4.59 s per loop

In [9]: %timeit rotate_with_deque(more_items, 800)
10 loops, best of 3: 109 ms per loop

非常有趣的是,这两种数据结构如何公开一个极为相似的接口,但性能却截然不同:)