web-dev-qa-db-ja.com

deque.popleft()およびlist.pop(0)。パフォーマンスに違いはありますか?

deque.popleft()list.pop(0)は同じ結果を返すようです。それらの間にパフォーマンスの違いはありますか?その理由は?

23
Bin

deque.popleft()はlist.pop(0)より高速です。これは、dequeがpopleft()をおよそO(1)で実行するように最適化されている一方、list.pop(0)はO(n)( deque objects を参照してください。

Dequeの_collectionsmodule.cとlistのlistobject.cのコメントとコードは、パフォーマンスの違いを説明する実装の洞察を提供します。つまり、両端結合オブジェクトを効果的に最適化する両端結合リストで構成される両端キューオブジェクトが、リストオブジェクトは単一リンクリストではなく、C配列(要素へのポインター)です( Python 2.7 listobject.h#l22 および Python 3.5 listobject.h#l2 )は、要素の高速ランダムアクセスに適していますが、再配置するためにO(n)時間を必要とします最初の要素を削除した後のすべての要素。

Python 2.7および3.5の場合、これらのソースコードファイルのURLは次のとおりです。

  1. https://hg.python.org/cpython/file/2.7/Modules/_collectionsmodule.c

  2. https://hg.python.org/cpython/file/2.7/Objects/listobject.c

  3. https://hg.python.org/cpython/file/3.5/Modules/_collectionsmodule.c

  4. https://hg.python.org/cpython/file/3.5/Objects/listobject.c

%timeitを使用すると、deque.popleft()とlist.pop(0)のパフォーマンスの差は、dequeとリストの両方に同じ52の要素がある場合、約4倍になり、長さが次の場合、1000倍以上になります。 10 ** 8。試験結果を以下に示します。

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
27
user4322779

パフォーマンスに違いはありますか?

はい。 deque.popleft() is O(1)-一定時間の操作。 list.pop(0)O(n)ですが、線形時間演算です。リストが大きいほど、時間がかかります。

どうして?

CPythonリストの実装は配列ベースです。 pop(0)はリストから最初の項目を削除し、ギャップを埋めるためにlen(lst) - 1項目を左にシフトする必要があります。

deque()実装は、二重にリンクされたリストを使用します。両端キューの大きさに関係なく、deque.popleft()には一定数(上記に制限)の操作が必要です。

10
jfs

はい、そしてあなたが長いリストまたは両端キューを持っているかどうかはかなりです。リスト内のすべての要素はメモリ内で隣接して配置されるため、要素を削除する場合、後続のすべての要素を左に1桁シフトする必要があるため、リストの先頭で要素を削除または挿入するのに必要な時間は、リストの長さ。一方、両端キューは、どちらかの最後で高速に挿入または削除できるように特別に構築されています(通常、両端キューの先頭にある「空の」メモリ位置を許可するか、ラップして両端キューが占めるメモリセグメントの最後には、実際には両端キューの先頭にあると見なされる要素を含めることができます。

次の2つのスニペットのパフォーマンスを比較します。

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))
6