Pregunta

¿Existe alguna diferencia de rendimiento entre tuplas y listas cuando se trata de creación de instancias y recuperación de elementos?

¿Fue útil?

Solución

El dis El módulo desensambla el código de bytes de una función y es útil para ver la diferencia entre tuplas y listas.

En este caso, puedes ver que acceder a un elemento genera código idéntico, pero que asignar una tupla es mucho más rápido que asignar una lista.

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

Otros consejos

En general, se podría esperar que las tuplas sean un poco más rápidas.Sin embargo, definitivamente deberías probar tu caso específico (si la diferencia puede afectar el rendimiento de tu programa, recuerda "la optimización prematura es la raíz de todos los males").

Python hace esto muy fácil: cronométralo es tu amigo.

$ 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

y...

$ 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

Entonces, en este caso, la creación de instancias es casi un orden de magnitud más rápida para la tupla, ¡pero el acceso a los elementos es en realidad algo más rápido para la lista!Entonces, si está creando algunas tuplas y accediendo a ellas muchas veces, puede que sea más rápido usar listas.

Por supuesto si quieres cambiar un elemento, la lista definitivamente será más rápida ya que necesitaría crear una tupla completamente nueva para cambiar un elemento (ya que las tuplas son inmutables).

Resumen

Las tuplas tienden a funcionar mejor que las listas en casi todas las categorías:

1) Las tuplas pueden ser plegado constante.

2) Las tuplas se pueden reutilizar en lugar de copiarlas.

3) Las tuplas son compactas y no se sobreasignan.

4) Las tuplas hacen referencia directa a sus elementos.

Las tuplas se pueden plegar de forma constante.

Las tuplas de constantes se pueden precalcular mediante el optimizador de mirilla de Python o el optimizador AST.Las listas, por otro lado, se crean desde cero:

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

No es necesario copiar las tuplas.

Correr tuple(some_tuple) regresa inmediatamente a sí mismo.Dado que las tuplas son inmutables, no es necesario copiarlas:

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

A diferencia de, list(some_list) requiere que todos los datos se copien a una nueva lista:

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

Las tuplas no sobreasignan

Dado que el tamaño de una tupla es fijo, se puede almacenar de forma más compacta que las listas que necesitan sobreasignarse para hacer adjuntar() operaciones eficientes.

Esto le da a las tuplas una buena ventaja de espacio:

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

Aquí está el comentario de Objetos/listaobjeto.c eso explica lo que están haciendo las listas:

/* 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.
 */

Las tuplas se refieren directamente a sus elementos.

Las referencias a objetos se incorporan directamente en un objeto tupla.Por el contrario, las listas tienen una capa adicional de direccionamiento indirecto a una matriz externa de punteros.

Esto le da a las tuplas una pequeña ventaja de velocidad para búsquedas indexadas y desempaquetado:

$ 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

Aquí así es como la tupla (10, 20) está almacenado:

    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;

Aquí así es como la lista [10, 20] está almacenado:

    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;

Tenga en cuenta que el objeto tupla incorpora los dos punteros de datos directamente, mientras que el objeto de lista tiene una capa adicional de direccionamiento indirecto a una matriz externa que contiene los dos punteros de datos.

Las tuplas, al ser inmutables, son más eficientes en memoria;listas, para mayor eficiencia, sobreasigne memoria para permitir adiciones sin constante reallocs.Entonces, si desea iterar a través de una secuencia constante de valores en su código (por ejemplo, for direction in 'up', 'right', 'down', 'left':), se prefieren las tuplas, ya que dichas tuplas se calculan previamente en tiempo de compilación.

Las velocidades de acceso deben ser las mismas (ambas se almacenan como matrices contiguas en la memoria).

Pero, alist.append(item) es mucho más preferido que atuple+= (item,) cuando se trata de datos mutables.Recuerde, las tuplas deben tratarse como registros sin nombres de campos.

También deberías considerar el array módulo en la biblioteca estándar si todos los elementos de su lista o tupla son del mismo tipo C.Requerirá menos memoria y puede ser más rápido.

Las tuplas deberían ser un poco más eficientes y, por eso, más rápidas que las listas porque son inmutables.

Aquí hay otro pequeño punto de referencia, solo por el simple hecho de hacerlo.

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)

Promedimos estos:

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

Puedes llamarlo casi inconcluso.

Pero claro, las tuplas tomaron 101.239% el tiempo, o 1.239% tiempo extra para hacer el trabajo en comparación con las listas.

La razón principal por la que Tuple es muy eficiente en la lectura es porque es inmutable.

¿Por qué los objetos inmutables son fáciles de leer?

La razón es que las tuplas se pueden almacenar en la memoria caché, a diferencia de las listas.El programa siempre lee desde la ubicación de la memoria de las listas, ya que es mutable (puede cambiar en cualquier momento).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top