Optimizaciones utilizadas en Python: lista y tupla

En Python, hay dos tipos similares: list (list) y tuple (tuple). La diferencia más famosa entre los dos es que las tuplas son inmutables.

No puede cambiar objetos en tupla:

>>> a = (1,2,3) >>> a[0] = 10 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment 

Pero puede modificar objetos mutables dentro de una tupla:

 >>> b = (1,[1,2,3],3) >>> b[1] [1, 2, 3] >>> b[1].append(4) >>> b (1, [1, 2, 3, 4], 3) 

Dentro de CPython (el intérprete estándar), la lista y la tupla se implementan como una hoja de punteros (enlaces) a objetos de Python, es decir. físicamente no almacenan objetos uno al lado del otro. Cuando elimina un objeto de la lista, se elimina la referencia a este objeto. Si alguien más se refiere al objeto, seguirá estando en la memoria.

Tuplas


A pesar de que las tuplas son mucho menos comunes en el código y no tan populares, este es un tipo muy fundamental que Python usa constantemente para fines internos.

Puede que no lo note, pero usa tuplas cuando:

  • trabajar con argumentos o parámetros (se almacenan como tuplas)
  • devolver dos o más variables de una función
  • iterar claves de valor en un diccionario
  • usar formato de cadena

Por lo general, el script más simple usa miles de tuplas:

 >>> import gc >>> def type_stats(type_obj): ... count = 0 ... for obj in gc.get_objects(): ... if type(obj) == type_obj: ... count += 1 ... return count ... >>> type_stats(tuple) 3136 >>> type_stats(list) 659 >>> import pandas >>> type_stats(tuple) 6953 >>> type_stats(list) 2455 

Listas vacías vs tuplas vacías


Una tupla vacía funciona como un singleton, es decir. siempre hay una tupla vacía en la memoria de un script Python en ejecución. Todas las tuplas vacías simplemente se refieren al mismo objeto, esto es posible porque las tuplas son inmutables. Este enfoque ahorra mucha memoria y acelera el proceso de trabajar con tuplas vacías.

 >>> a = () >>> b = () >>> a is b True >>> id(a) 4409020488 >>> id(b) 4409020488 >>> #  CPython,  id    . 

Pero esto no funciona con las listas, porque se pueden cambiar:

 >>> a = [] >>> b = [] >>> a is b False >>> id(a) 4465566920 >>> id(b) 4465370632 

Optimizar la asignación de memoria para tuplas


Para reducir la fragmentación de la memoria y acelerar la creación de tuplas, Python reutiliza las tuplas antiguas que se han eliminado. Si una tupla consta de menos de 20 elementos y ya no se usa, entonces, en lugar de eliminarla, Python la coloca en una lista especial que almacena las tuplas que son libres de reutilización.

Esta lista se divide en 20 grupos, donde cada grupo es una lista de tuplas de tamaño n, donde n es de 0 a 20. Cada grupo puede almacenar hasta 2,000 tuplas gratis. El primer grupo almacena solo un elemento y es una lista de una tupla vacía.

 >>> a = (1,2,3) >>> id(a) 4427578104 >>> del a >>> b = (1,2,4) >>> id(b) 4427578104 

En el ejemplo anterior, podemos ver que ayb tienen la misma dirección en la memoria. Esto se debe al hecho de que instantáneamente tomamos una tupla gratis del mismo tamaño.

Optimizar la asignación de memoria para listas


Dado que las listas están sujetas a cambios, la misma optimización que en el caso de las tuplas ya no se puede poner en marcha. A pesar de esto, las listas usan optimizaciones similares dirigidas a listas vacías. Si se elimina la lista vacía, también se puede reutilizar en el futuro.

 >>> a = [] >>> id(a) 4465566792 >>> del a >>> b = [] >>> id(b) 4465566792 

Cambiar tamaño de lista


Para evitar la sobrecarga de cambiar constantemente el tamaño de las listas, Python no cambia el tamaño cada vez que es necesario. En cambio, cada lista tiene un conjunto de celdas adicionales que están ocultas para el usuario, pero que luego pueden usarse para nuevos elementos. Tan pronto como terminan las celdas ocultas, Python agrega espacio adicional para nuevos elementos. Y lo hace con un buen margen, la cantidad de celdas ocultas se selecciona en función del tamaño actual de la lista: cuanto más grande es, más espacios ocultos adicionales para nuevos elementos.

Esta optimización es especialmente útil cuando intenta agregar muchos elementos en un bucle.

El patrón de crecimiento del tamaño de la lista se ve así: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...

Por ejemplo, si desea agregar un nuevo elemento a una lista con 8 elementos, entonces no habrá celdas libres y Python expandirá inmediatamente su tamaño a 16 celdas, donde 9 de ellas estarán ocupadas y visibles para el usuario.

Fórmula de tamaño de Python:

 >>> def get_new_size(n_items): ... new_size = n_items + (n_items // 2 ** 3) ... if n_items < 9: ... new_size += 3 ... else: ... new_size += 6 ... ... return new_size ... >>> get_new_size(9) 16 

Velocidad


Si comparamos estos dos tipos de velocidad, en promedio en un hospital, las tuplas son un poco más rápidas que las listas. Raymond Hettinger tiene una gran explicación para la diferencia de velocidad en stackoverflow .

PD: Soy el autor de este artículo, puedes hacer cualquier pregunta.

Source: https://habr.com/ru/post/es417783/


All Articles