使用Python列表作为队列的效率

使用Python列表作为队列的效率,python,list,memory-leaks,Python,List,Memory Leaks,一位同事最近编写了一个程序,其中他使用Python列表作为队列。换句话说,他在需要插入项目时使用.append(x),在需要删除项目时使用.pop(0) 我知道Python已经完成了,我正试图弄清楚是否要花我(有限的)时间重写这段代码以使用它。假设我们执行了数百万个附件和POP,但从来没有超过几千个条目,他的列表使用会有问题吗 特别是,Python列表实现所使用的底层数组是否会无限期地增长,即使列表中只有上千个元素,它也会有数百万个点,或者Python最终会做一个realloc并释放一些内存吗?

一位同事最近编写了一个程序,其中他使用Python列表作为队列。换句话说,他在需要插入项目时使用
.append(x)
,在需要删除项目时使用
.pop(0)

我知道Python已经完成了,我正试图弄清楚是否要花我(有限的)时间重写这段代码以使用它。假设我们执行了数百万个附件和POP,但从来没有超过几千个条目,他的列表使用会有问题吗


特别是,Python列表实现所使用的底层数组是否会无限期地增长,即使列表中只有上千个元素,它也会有数百万个点,或者Python最终会做一个
realloc
并释放一些内存吗?

听起来,在这里进行一点经验测试可能是最好的做法-二阶问题可能会使一种方法在实践中更好,即使在理论上不是更好。

使用
列表实现不会耗尽内存,但表现将很差。发件人:

虽然
list
对象支持类似的 操作方面,它们针对以下方面进行了优化: 快速的定长操作并产生错误 O(n)的内存移动成本
pop(0)
insert(0,v)
操作 这会改变大小和尺寸 基础数据的位置 代表性

因此,使用
deque
会快得多。

每个
.pop(0)
需要N个步骤,因为必须重新组织列表。所需的内存不会无休止地增长,只会与所持有的项目所需的内存一样大

我建议使用
deque
从前端获取O(1)append和pop。

一些答案声称,当两者都有1000个条目时,deque与用作FIFO的列表相比,速度优势为“10倍”,但这有点过头了:

$ python -mtimeit -s'q=range(1000)' 'q.append(23); q.pop(0)'
1000000 loops, best of 3: 1.24 usec per loop
$ python -mtimeit -s'import collections; q=collections.deque(range(1000))' 'q.append(23); q.popleft()'
1000000 loops, best of 3: 0.573 usec per loop
python-mtimeit
是您的朋友——一种非常有用且简单的微基准测试方法!当然,使用它,您还可以在更小的情况下轻松探索性能:

$ python -mtimeit -s'q=range(100)' 'q.append(23); q.pop(0)'
1000000 loops, best of 3: 0.972 usec per loop
$ python -mtimeit -s'import collections; q=collections.deque(range(100))' 'q.append(23); q.popleft()'
1000000 loops, best of 3: 0.576 usec per loop
(顺便说一句,12件物品与100件物品没有太大区别),更大的物品:

$ python -mtimeit -s'q=range(10000)' 'q.append(23); q.pop(0)'
100000 loops, best of 3: 5.81 usec per loop
$ python -mtimeit -s'import collections; q=collections.deque(range(10000))' 'q.append(23); q.popleft()'
1000000 loops, best of 3: 0.574 usec per loop
您可以看到,deque的O(1)性能的说法是有根据的,而一个列表的速度是1000个项目的两倍多,大约10000个数量级。您还可以看到,即使在这种情况下,每个append/pop对也只会浪费5微秒左右的时间,并决定这种浪费有多大(尽管如果您使用该容器就是这么做的,deque没有任何缺点,所以您最好切换,即使5个usec或多或少不会产生重大影响)。

来自Beazley,p。194:

一些库模块提供了新的类型 它的性能超过了 某些任务。例如, collections.deque类型提供 类似于列表的功能,但 已针对 在两端插入项目。A. 相比之下,列表是唯一有效的 在末尾追加项目时。如果 您可以在前面插入项目,所有 其他因素需要改变 为了腾出空间。时间 随着列表的增加,需要执行此操作 变得越来越大。只是给 如果你想知道其中的区别,这里有一个在列表前面插入一百万个项目的计时度量:

下面是代码示例:

>>> from timeit import timeit
>>> timeit('s.appendleft(37)', 'import collections; s = collections.deque()', number=1000000)
0.13162776274638258
>>> timeit('s.insert(0,37)', 's = []', number=1000000)
932.07849908298408
计时来自我的机器


2012-07-01更新

>>> from timeit import timeit
>>> n = 1024 * 1024
>>> while n > 1:
...     print '-' * 30, n
...     timeit('s.appendleft(37)', 'import collections; s = collections.deque()', number=n)
...     timeit('s.insert(0,37)', 's = []', number=n)
...     n >>= 1
... 
------------------------------ 1048576
0.1239769458770752
799.2552740573883
------------------------------ 524288
0.06924104690551758
148.9747350215912
------------------------------ 262144
0.029170989990234375
35.077512979507446
------------------------------ 131072
0.013737916946411133
9.134140014648438
------------------------------ 65536
0.006711006164550781
1.8818109035491943
------------------------------ 32768
0.00327301025390625
0.48307204246520996
------------------------------ 16384
0.0016388893127441406
0.11021995544433594
------------------------------ 8192
0.0008249282836914062
0.028419017791748047
------------------------------ 4096
0.00044918060302734375
0.00740504264831543
------------------------------ 2048
0.00021195411682128906
0.0021741390228271484
------------------------------ 1024
0.00011205673217773438
0.0006101131439208984
------------------------------ 512
6.198883056640625e-05
0.00021386146545410156
------------------------------ 256
2.9087066650390625e-05
8.797645568847656e-05
------------------------------ 128
1.5974044799804688e-05
3.600120544433594e-05
------------------------------ 64
8.821487426757812e-06
1.9073486328125e-05
------------------------------ 32
5.0067901611328125e-06
1.0013580322265625e-05
------------------------------ 16
3.0994415283203125e-06
5.9604644775390625e-06
------------------------------ 8
3.0994415283203125e-06
5.0067901611328125e-06
------------------------------ 4
3.0994415283203125e-06
4.0531158447265625e-06
------------------------------ 2
2.1457672119140625e-06
2.86102294921875e-06

“快多了”?或者可能更快?对于大小为1000或10倍的列表。在我的书中,超过一个数量级是“快得多”的。洛特:从列表中弹出的是O(N),从deque中弹出的是O(1)。@John,你大错特错了:CPython同时使用引用计数和标记与扫描,分代垃圾收集(在某种程度上,你可以通过标准库中的gc模块控制)。所以“CPython不使用垃圾收集”确实是一个有严重缺陷的语句。实际上,使用列表可能会耗尽内存。Deque分配在不必彼此相邻的存储桶中,因此基本上您可以创建一个与可用内存一样大的Deque。然而,列表是数组,必须连续分配,如果它们的大小达到兆字节,您可能会发现这会给您带来一些麻烦(而且它们至少可能由于重新分配而导致严重的内存碎片)。底层不会无限期地继续增长(只会比其“高水位线”稍微大一点)。但是在一些答案中强调的O(N)与O(1)问题可能很重要。谢谢,这些测试非常有用。