web-dev-qa-db-ja.com

Pythonリストをキューとして使用する効率

最近、同僚がPythonリストをキューとして使用するプログラムを作成しました。つまり、アイテムを挿入する必要がある場合は.append(x)を使用し、アイテムを削除する必要がある場合は.pop(0)を使用しました。

Pythonには collections.deque があり、このコードを使用するために(限られた)時間をかけてこのコードを書き換えるかどうかを考えています。何百万もの追加とポップを実行するが、数千を超えるエントリが存在しないと仮定すると、彼のリストの使用に問題がありますか?

特に、Pythonリストの実装で使用される基になる配列は、リストが1,000個しかない場合でも無数に増え続け、数百万個のスポットが発生するか、Pythonが最終的にreallocを実行して一部を解放しますその記憶の?

43
Eli Courtwright

list実装を使用してメモリ不足になることはありませんが、パフォーマンスは低下します。 the docs から:

listオブジェクトは同様の操作をサポートしますが、これらは高速固定長操作用に最適化されており、pop(0)およびinsert(0, v)基になるデータ表現のサイズと位置の両方を変更する操作。

したがって、dequeを使用するとはるかに高速になります。

32
John Millikin

いくつかの回答は、両方とも1000のエントリがある場合、dequeとlist-used-as-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

(100アイテムではなく12アイテムについてはそれほど大きな違いはありません)。

$ 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

O(1) dequeのパフォーマンスは十分に根拠がありますが、リストは約1,000アイテムの約2倍、10,000桁のオーダーです)と主張しています。そのような場合でも、アペンド/ポップのペアごとに5マイクロ秒程度しか無駄にしておらず、その無駄がどれほど重要であるかを決定します(それがそのコンテナーで実行しているすべての場合、dequeには欠点がないため、切り替えることもできます) 5 usecが多かれ少なかれ重要な違いにならない場合でも)。

71
Alex Martelli

Beazleyの Python Essential Reference、Fourth Edition から、p。 194:

一部のライブラリモジュールは、特定のタスクで組み込みよりも優れた新しいタイプを提供します。たとえば、collections.dequeタイプはリストと同様の機能を提供しますが、両端にアイテムを挿入するために高度に最適化されています。対照的に、リストは最後に項目を追加する場合にのみ効率的です。アイテムを前面に挿入する場合、他のすべての要素は、スペースを空けるためにシフトする必要があります。リストが大きくなるにつれて、これを実行するために必要な時間は長くなります。違いを理解するために、リストと両端キューの前に100万個のアイテムを挿入するタイミング測定を次に示します。

そして、このコードサンプルに従います:

>>> 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
16
hughdbrown

リストを再編成する必要があるため、すべての.pop(0)はNステップを実行します。必要なメモリは無限に大きくなることはなく、保持されるアイテムに必要なだけの大きさになります。

dequeを使用してO(1)を追加し、前面からポップすることをお勧めします。

4
bayer

少し実験的なテストがここで行うのが最善のように思えます。2次の問題は、理論的にはより優れていなくても、1つのアプローチが実際により良くなる可能性があります。

2
Peter