it-swarm.it

Le tuple sono più efficienti delle liste in Python?

C'è qualche differenza di prestazioni tra tuple ed elenchi quando si tratta di istanziazione e recupero di elementi?

191
Readonly

Il modulo dis 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 l'assegnazione di una Tupla è molto più rapida dell'assegnazione di 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
152
Mark Harrison

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 vuoi cambiare un elemento, l'elenco sarà sicuramente più veloce poiché dovrai creare un'intera nuova Tupla per cambiare un elemento di (poiché le tuple sono immutabili).

194
dF.

Sommario

Le tuple tendono ad avere un rendimento migliore degli elenchi in quasi tutte le categorie:

1) Le tuple possono essere costante piegato .

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

L'esecuzione di Tuple(some_Tuple) restituisce 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 vengano 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 è fisso, può essere memorizzato più compatto di elenchi che necessità di sovra-assegnano fare append () operazioni efficienti.

Questo dà alle tuple un piacevole vantaggio di spazio:

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

Ecco il commento di 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 Tuple. 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

Qui è come è memorizzata la Tupla (10, 20):

    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;

Qui è come è memorizzato l'elenco [10, 20]:

    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 i due puntatori ai dati direttamente mentre l'oggetto lista ha un ulteriore livello di riferimento indiretto ad un array esterno che tiene i due puntatori ai dati.

166

Le tuple, essendo immutabili, sono più efficienti in termini di memoria; elenca, per efficienza, allocazione eccessiva della memoria per consentire aggiunte senza costanti realloc. Quindi, se vuoi iterare attraverso una sequenza costante di valori nel tuo codice (es. for direction in 'up', 'right', 'down', 'left':), Le tuple sono preferite, 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 a atuple+= (item,) quando hai a che fare con dati mutabili. Ricorda che le tuple devono essere trattate come record senza nomi di campo.

31
tzot

Dovresti anche considerare il modulo array 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.

9
Ralph Corderoy

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

4
ctcherry

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 preso 101.239% l'ora o 1.239% tempo extra per fare il lavoro rispetto alle liste.

3
Dev Aggarwal