web-dev-qa-db-fra.com

deque.popleft () et list.pop (0). Y a-t-il une différence de performances?

deque.popleft() et list.pop(0) semblent retourner le même résultat. Y a-t-il une différence de performances entre eux et pourquoi?

23
Bin

deque.popleft () est plus rapide que list.pop (0), car le deque a été optimisé pour faire popleft () approximativement dans O (1), tandis que list.pop (0) prend O(n) ( voir deque objects ).

Les commentaires et le code dans _collectionsmodule.c pour deque et listobject.c pour list fournissent des informations d'implémentation pour expliquer les différences de performances. À savoir qu'un objet deque "est composé d'une liste doublement liée", qui optimise efficacement les ajouts et les sauts aux deux extrémités, tandis que les objets de liste ne sont même pas des listes liées individuellement mais des tableaux C (de pointeurs vers des éléments (voir Python 2.7 listobject.h # l22 et Python 3.5 listobject.h # l2 ), ce qui les rend bons pour un accès aléatoire rapide aux éléments mais nécessite O(n) temps pour se repositionner tous les éléments après le retrait du premier.

Pour Python 2.7 et 3.5, les URL de ces fichiers de code source sont:

  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

En utilisant% timeit, la différence de performances entre deque.popleft () et list.pop (0) est d'environ un facteur 4 lorsque le deque et la liste ont les mêmes 52 éléments et augmente jusqu'à un facteur de 1000 lorsque leur longueur est 10 ** 8. Les résultats des tests sont donnés ci-dessous.

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

Y a-t-il une différence de performances?

Oui. deque.popleft() est O(1) - une opération à temps constant. Alors que list.pop(0) est O(n) - opération de temps linéaire: plus la liste est longue, plus elle prend de temps.

Pourquoi?

L'implémentation de la liste CPython est basée sur un tableau. pop(0) supprime le premier élément de la liste et il faut décaler vers la gauche len(lst) - 1 éléments pour combler le vide.

L'implémentation de deque() utilise une liste doublement liée. Quelle que soit la taille de la deque, deque.popleft() nécessite un nombre d'opérations constant (limité ci-dessus).

10
jfs

Oui, et c'est considérable si vous avez une longue liste ou deque. Tous les éléments d'une liste sont placés de manière contiguë dans la mémoire, donc si vous supprimez un élément, tous les éléments suivants doivent être décalés d'une position vers la gauche - par conséquent, le temps requis pour supprimer ou insérer un élément au début d'une liste est proportionnel à la longueur de la liste. Un deque, d'autre part, est spécifiquement conçu pour permettre des insertions ou des retraits rapides à soit fin (généralement en autorisant des emplacements de mémoire "vides" au début du deque, ou pour envelopper de manière à ce que le fin du segment de mémoire occupé par le deque peut contenir des éléments qui sont réellement considérés comme étant au début du deque).

Comparez les performances de ces deux extraits:

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
Aasmund Eldhuset