Ole*_*nko 35 python performance benchmarking deque data-structures
在python文档中,我可以看到deque是一个特别的集合,高度优化从左侧或右侧弹出/添加项目.例如文件说:
Deques是堆栈和队列的概括(名称发音为"deck",是"双端队列"的缩写).Deques支持线程安全,内存有效的附加和从双端队列的弹出,在任一方向上具有大致相同的O(1)性能.
尽管列表对象支持类似的操作,但它们针对快速固定长度操作进行了优化,并导致pop(0)和insert(0,v)操作的O(n)内存移动成本,这些操作改变了底层数据表示的大小和位置.
我决定使用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
Run Code Online (Sandbox Code Playgroud)
Ray*_*ger 74
Could anyone explain me what I did wrong here
是的,您的时间由创建列表或双端队列的时间决定.相比之下,做流行音乐的时间微不足道.
相反,你应该从设置时间中隔离你想要测试的东西(弹出速度):
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
Run Code Online (Sandbox Code Playgroud)
也就是说,deques和list在性能方面的真正区别是:
Deques对appendleft()和popleft()的速度为O(1),而list对于insert(0,value)和pop(0)具有O(n)性能.
列表附加性能是命中和未命中,因为它在引擎盖下使用realloc().因此,它往往在简单代码中具有过度乐观的时序(因为realloc不必移动数据)并且实际代码中的时间确实很慢(因为碎片强制重新分配以移动所有数据).相比之下,deque追加性能是一致的,因为它永远不会重新分配并且永远不会移动数据.
sbe*_*rry 20
对于它的价值:
> 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
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,它真正的亮点是在appendleft
VS insert
.
我找到了解决这个问题的方法,并认为我会提供一个带有一点上下文的示例。
使用 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
Run Code Online (Sandbox Code Playgroud)
注意:这是 deque 的一种常见用法,deque 有一个内置
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
Run Code Online (Sandbox Code Playgroud)
非常有趣的是,这两种数据结构如何公开一个非常相似的界面,但性能却截然不同:)
小智 6
我建议您参考 https://wiki.python.org/moin/TimeComplexity
Python 列表和双端队列对于大多数操作(推送、弹出等)具有相似的复杂性