Le tuple sono più efficienti delle liste in Python?


Risposte:


172

Il dismodulo disassembla il codice byte per una funzione ed è utile per vedere la differenza tra tuple ed elenchi.

In questo caso, puoi vedere che l'accesso a un elemento genera un codice identico, ma che assegnare una tupla è molto più veloce che assegnare un elenco.

>>> def a():
...     x=[1,2,3,4,5]
...     y=x[2]
...
>>> def b():
...     x=(1,2,3,4,5)
...     y=x[2]
...
>>> import dis
>>> dis.dis(a)
  2           0 LOAD_CONST               1 (1)
              3 LOAD_CONST               2 (2)
              6 LOAD_CONST               3 (3)
              9 LOAD_CONST               4 (4)
             12 LOAD_CONST               5 (5)
             15 BUILD_LIST               5
             18 STORE_FAST               0 (x)

  3          21 LOAD_FAST                0 (x)
             24 LOAD_CONST               2 (2)
             27 BINARY_SUBSCR
             28 STORE_FAST               1 (y)
             31 LOAD_CONST               0 (None)
             34 RETURN_VALUE
>>> dis.dis(b)
  2           0 LOAD_CONST               6 ((1, 2, 3, 4, 5))
              3 STORE_FAST               0 (x)

  3           6 LOAD_FAST                0 (x)
              9 LOAD_CONST               2 (2)
             12 BINARY_SUBSCR
             13 STORE_FAST               1 (y)
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE

66
Err, solo che lo stesso bytecode sia generato assolutamente non significa che le stesse operazioni avvengano a livello C (e quindi cpu). Prova a creare una classe ListLikecon una __getitem__che fa qualcosa di orribilmente lento, quindi disassembla x = ListLike((1, 2, 3, 4, 5)); y = x[2]. Il bytecode sarà più simile all'esempio di tupla sopra che all'esempio di elenco, ma credi davvero che le prestazioni saranno simili?
mzz

2
Sembra che tu stia dicendo che alcuni tipi sono più efficienti di altri. Ciò ha senso, ma l'overhead delle generazioni di elenchi e tuple sembra essere ortogonale al tipo di dati in questione, con l'avvertenza che si tratta di elenchi e tuple dello stesso tipo di dati.
Mark Harrison,

11
Il numero di codici byte, come il numero di righe di codice, ha poca relazione con la velocità di esecuzione (e quindi l'efficienza e le prestazioni).
martineau,

18
Sebbene il suggerimento di poter concludere qualsiasi cosa dal conteggio delle operazioni sia errato, ciò mostra la differenza fondamentale: le tuple costanti sono memorizzate come tali nel bytecode e appena citate quando utilizzate, mentre gli elenchi devono essere compilati in fase di esecuzione.
poolie

6
Questa risposta ci mostra che Python riconosce le costanti di tupla. Buono a sapersi! Ma cosa succede quando si tenta di creare una tupla o un elenco da valori variabili?
Tom,

211

In generale, potresti aspettarti che le tuple siano leggermente più veloci. Tuttavia, dovresti assolutamente testare il tuo caso specifico (se la differenza potrebbe influire sulle prestazioni del tuo programma, ricorda "l'ottimizzazione prematura è la radice di tutti i mali").

Python lo rende molto semplice: timeit è tuo amico.

$ python -m timeit "x=(1,2,3,4,5,6,7,8)"
10000000 loops, best of 3: 0.0388 usec per loop

$ python -m timeit "x=[1,2,3,4,5,6,7,8]"
1000000 loops, best of 3: 0.363 usec per loop

e...

$ python -m timeit -s "x=(1,2,3,4,5,6,7,8)" "y=x[3]"
10000000 loops, best of 3: 0.0938 usec per loop

$ python -m timeit -s "x=[1,2,3,4,5,6,7,8]" "y=x[3]"
10000000 loops, best of 3: 0.0649 usec per loop

Quindi, in questo caso, l'istanza è quasi un ordine di grandezza più veloce per la tupla, ma l'accesso agli oggetti è in realtà un po 'più veloce per l'elenco! Quindi, se stai creando alcune tuple e accedendo a loro molte volte, in realtà potrebbe essere più veloce usare gli elenchi.

Ovviamente se si desidera modificare un elemento, l'elenco sarà sicuramente più veloce poiché sarebbe necessario creare una tupla completamente nuova per cambiarne uno (poiché le tuple sono immutabili).


3
Con quale versione di Python sono stati testati!
Matt Joiner,

2
V'è un altro test interessante - python -m timeit "x=tuple(xrange(999999))"vs python -m timeit "x=list(xrange(999999))". Come ci si potrebbe aspettare, ci vuole un po 'più di tempo per materializzare una tupla rispetto a un elenco.
Hamish Grubijan,

3
Sembra strano che l'accesso alle tuple sia più lento dell'accesso all'elenco. Tuttavia, provando che in Python 2.7 sul mio PC Windows 7, la differenza è solo del 10%, quindi non importante.
ToolmakerSteve

51
FWIW, l'accesso alle liste è più veloce dell'accesso in tuple in Python 2 ma solo perché esiste un caso speciale per le liste in BINARY_SUBSCR in Python / ceval.c. In Python 3 tale ottimizzazione è sparita e l'accesso alle tuple diventa leggermente più veloce dell'accesso all'elenco.
Raymond Hettinger,

3
@yoopoo, il primo test crea un elenco un milione di volte, ma il secondo crea un elenco una volta e vi accede un milione di volte. Il -s "SETUP_CODE"viene eseguito prima del codice a tempo reale.
leewz,

203

Sommario

Le tuple tendono a funzionare meglio degli elenchi in quasi tutte le categorie:

1) Le tuple possono essere piegate costantemente .

2) Le tuple possono essere riutilizzate anziché copiate.

3) Le tuple sono compatte e non allocano eccessivamente.

4) Le tuple fanno riferimento direttamente ai loro elementi.

Le tuple possono essere piegate costantemente

Tuple di costanti possono essere pre-calcolate dall'ottimizzatore spioncino di Python o dall'ottimizzatore AST. Gli elenchi, d'altra parte, vengono creati da zero:

    >>> from dis import dis

    >>> dis(compile("(10, 'abc')", '', 'eval'))
      1           0 LOAD_CONST               2 ((10, 'abc'))
                  3 RETURN_VALUE   

    >>> dis(compile("[10, 'abc']", '', 'eval'))
      1           0 LOAD_CONST               0 (10)
                  3 LOAD_CONST               1 ('abc')
                  6 BUILD_LIST               2
                  9 RETURN_VALUE 

Le tuple non devono essere copiate

La corsa tuple(some_tuple)ritorna immediatamente se stessa. Poiché le tuple sono immutabili, non devono essere copiate:

>>> a = (10, 20, 30)
>>> b = tuple(a)
>>> a is b
True

Al contrario, list(some_list)richiede che tutti i dati siano copiati in un nuovo elenco:

>>> a = [10, 20, 30]
>>> b = list(a)
>>> a is b
False

Le tuple non allocano eccessivamente

Poiché le dimensioni di una tupla sono fisse, possono essere archiviate in modo più compatto rispetto agli elenchi che devono essere allocati eccessivamente per rendere efficienti le operazioni append () .

Questo dà alle tuple un bel vantaggio di spazio:

>>> import sys
>>> sys.getsizeof(tuple(iter(range(10))))
128
>>> sys.getsizeof(list(iter(range(10))))
200

Ecco il commento da Objects / listobject.c che spiega cosa stanno facendo gli elenchi:

/* This over-allocates proportional to the list size, making room
 * for additional growth.  The over-allocation is mild, but is
 * enough to give linear-time amortized behavior over a long
 * sequence of appends() in the presence of a poorly-performing
 * system realloc().
 * The growth pattern is:  0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
 * Note: new_allocated won't overflow because the largest possible value
 *       is PY_SSIZE_T_MAX * (9 / 8) + 6 which always fits in a size_t.
 */

Le tuple si riferiscono direttamente ai loro elementi

I riferimenti agli oggetti sono incorporati direttamente in un oggetto tupla. Al contrario, gli elenchi hanno un ulteriore livello di riferimento indiretto a una matrice esterna di puntatori.

Ciò offre alle tuple un piccolo vantaggio di velocità per le ricerche indicizzate e il disimballaggio:

$ python3.6 -m timeit -s 'a = (10, 20, 30)' 'a[1]'
10000000 loops, best of 3: 0.0304 usec per loop
$ python3.6 -m timeit -s 'a = [10, 20, 30]' 'a[1]'
10000000 loops, best of 3: 0.0309 usec per loop

$ python3.6 -m timeit -s 'a = (10, 20, 30)' 'x, y, z = a'
10000000 loops, best of 3: 0.0249 usec per loop
$ python3.6 -m timeit -s 'a = [10, 20, 30]' 'x, y, z = a'
10000000 loops, best of 3: 0.0251 usec per loop

Ecco come (10, 20)viene memorizzata la tupla :

    typedef struct {
        Py_ssize_t ob_refcnt;
        struct _typeobject *ob_type;
        Py_ssize_t ob_size;
        PyObject *ob_item[2];     /* store a pointer to 10 and a pointer to 20 */
    } PyTupleObject;

Ecco come [10, 20]viene memorizzato l'elenco :

    PyObject arr[2];              /* store a pointer to 10 and a pointer to 20 */

    typedef struct {
        Py_ssize_t ob_refcnt;
        struct _typeobject *ob_type;
        Py_ssize_t ob_size;
        PyObject **ob_item = arr; /* store a pointer to the two-pointer array */
        Py_ssize_t allocated;
    } PyListObject;

Si noti che l'oggetto tupla incorpora direttamente i due puntatori di dati mentre l'oggetto elenco ha un ulteriore livello di riferimento indiretto a un array esterno che contiene i due puntatori di dati.


19
Finalmente qualcuno mette le strutture a C!
osman,

1
Internally, tuples are stored a little more efficiently than lists, and also tuples can be accessed slightly faster. Come hai potuto spiegare i risultati dalla risposta di dF. Allora?
DRz,

5
Quando si lavora con ~ 50k elenchi di ~ 100 elenchi di elementi, spostando questa struttura su tuple si riducono i tempi di ricerca di più ordini di grandezza per più ricerche. Credo che ciò sia dovuto alla maggiore localizzazione della cache della tupla una volta che inizi a utilizzare la tupla a causa della rimozione del secondo livello di indiretta che dimostri.
horta,

tuple(some_tuple)restituisce some_tuplese stesso solo se some_tupleè hash - quando il suo contenuto è ricorsivamente immutabile e hassable. Altrimenti, tuple(some_tuple)restituisce una nuova tupla. Ad esempio, quando some_tuplecontiene elementi mutabili.
Luciano Ramalho,

Le tuple non sono sempre più veloci. Considera `` `t = () per i nell'intervallo (1.100): t + = il = [] per i nell'intervallo (1.1000): a.append (i)` `` Il secondo è più veloce
melvil james

32

Le tuple, essendo immutabili, sono più efficienti in termini di memoria; elenca, per efficienza, allocazione eccessiva della memoria al fine di consentire aggiunte senza costanti realloc. Quindi, se vuoi iterare attraverso una sequenza costante di valori nel tuo codice (ad es. for direction in 'up', 'right', 'down', 'left':), Sono preferite le tuple, poiché tali tuple sono pre-calcolate in tempo di compilazione.

Le velocità di accesso dovrebbero essere le stesse (sono entrambe memorizzate come array contigui nella memoria).

Ma, alist.append(item) è molto preferito atuple+= (item,)quando si tratta di dati mutabili. Ricorda che le tuple devono essere trattate come record senza nomi di campo.


1
qual è il tempo di compilazione in Python?
Balki,

1
@balki: l'ora in cui la sorgente python viene compilata in bytecode (che bytecode potrebbe essere salvato come file .py [co]).
Tzot

Una citazione sarebbe ottima se possibile.
Grijesh Chauhan,

9

Dovresti anche considerare il arraymodulo nella libreria standard se tutti gli elementi nella tua lista o tupla sono dello stesso tipo C. Ci vorrà meno memoria e può essere più veloce.


15
Ci vorrà meno memoria, ma il tempo di accesso sarà probabilmente un po 'più lento, piuttosto che più veloce. L'accesso a un elemento richiede che il valore compresso sia unboxed su un numero intero reale, che rallenterà il processo.
Brian,

5

Ecco un altro piccolo punto di riferimento, solo per il gusto di farlo ..

In [11]: %timeit list(range(100))
749 ns ± 2.41 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [12]: %timeit tuple(range(100))
781 ns ± 3.34 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [1]: %timeit list(range(1_000))
13.5 µs ± 466 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [2]: %timeit tuple(range(1_000))
12.4 µs ± 182 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [7]: %timeit list(range(10_000))
182 µs ± 810 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [8]: %timeit tuple(range(10_000))
188 µs ± 2.38 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [3]: %timeit list(range(1_00_000))
2.76 ms ± 30.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [4]: %timeit tuple(range(1_00_000))
2.74 ms ± 31.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [10]: %timeit list(range(10_00_000))
28.1 ms ± 266 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [9]: %timeit tuple(range(10_00_000))
28.5 ms ± 447 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Facciamo una media di questi:

In [3]: l = np.array([749 * 10 ** -9, 13.5 * 10 ** -6, 182 * 10 ** -6, 2.76 * 10 ** -3, 28.1 * 10 ** -3])

In [2]: t = np.array([781 * 10 ** -9, 12.4 * 10 ** -6, 188 * 10 ** -6, 2.74 * 10 ** -3, 28.5 * 10 ** -3])

In [11]: np.average(l)
Out[11]: 0.0062112498000000006

In [12]: np.average(t)
Out[12]: 0.0062882362

In [17]: np.average(t) / np.average(l)  * 100
Out[17]: 101.23946713590554

Puoi chiamarlo quasi inconcludente.

Ma certo, le tuple hanno impiegato 101.239%del tempo o 1.239%tempo extra per fare il lavoro rispetto alle liste.


4

Le tuple dovrebbero essere leggermente più efficienti e per questo, più veloci degli elenchi perché sono immutabili.


4
Perché dici che l'immutabilità, in sé e per sé, aumenta l'efficienza? Soprattutto efficienza di istanziazione e recupero?
Blair Conrad,

1
Sembra che la risposta di Mark sopra la mia abbia coperto le istruzioni smontate di ciò che accade all'interno di Python. Puoi vedere che l'istanza richiede meno istruzioni, tuttavia in questo caso il recupero è apparentemente identico.
ctcherry,

le tuple immutabili hanno un accesso più rapido rispetto alle liste mutabili
noobninja,

-6

Il motivo principale per cui Tuple è molto efficiente nella lettura è perché è immutabile.

Perché gli oggetti immutabili sono facili da leggere?

Il motivo è che le tuple possono essere archiviate nella cache di memoria, a differenza degli elenchi. Il programma legge sempre dalla posizione della memoria degli elenchi in quanto è modificabile (può cambiare in qualsiasi momento).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.