A tuple
ocupa menos espacio de memoria en Python:
>>> a = (1,2,3)
>>> a.__sizeof__()
48
mientras que list
s ocupa más espacio en la memoria:
>>> b = [1,2,3]
>>> b.__sizeof__()
64
¿Qué sucede internamente en la gestión de memoria de Python?
A tuple
ocupa menos espacio de memoria en Python:
>>> a = (1,2,3)
>>> a.__sizeof__()
48
mientras que list
s ocupa más espacio en la memoria:
>>> b = [1,2,3]
>>> b.__sizeof__()
64
¿Qué sucede internamente en la gestión de memoria de Python?
Respuestas:
Supongo que está usando CPython y con 64 bits (obtuve los mismos resultados en mi CPython 2.7 de 64 bits). Puede haber diferencias en otras implementaciones de Python o si tiene un Python de 32 bits.
Independientemente de la implementación,
list
los mensajes de correo electrónico son de tamaño variable mientras quetuple
s son de tamaño fijo.Entonces
tuple
s pueden almacenar los elementos directamente dentro de la estructura, las listas, por otro lado, necesitan una capa de indirección (almacena un puntero a los elementos). Esta capa de indirección es un puntero, en sistemas de 64 bits que son 64 bits, por lo tanto, 8 bytes.Pero hay otra cosa que
list
sí: sobreasignan. Delist.append
lo contrario , sería unaO(n)
operación siempre : para que se amorticeO(1)
(¡mucho más rápido!), Se sobreasigna. Pero ahora tiene que realizar un seguimiento del tamaño asignado y el tamaño de relleno (tuple
solo es necesario almacenar un tamaño, porque el tamaño asignado y el tamaño de relleno son siempre idénticos). Eso significa que cada lista tiene que almacenar otro "tamaño" que en los sistemas de 64 bits es un número entero de 64 bits, de nuevo 8 bytes.Entonces, los
list
s necesitan al menos 16 bytes más de memoria que lostuple
s. ¿Por qué dije "al menos"? Debido a la sobreasignación. La sobreasignación significa que asigna más espacio del necesario. Sin embargo, la cantidad de sobreasignación depende de "cómo" crea la lista y el historial de anexos / eliminaciones:Imagenes
Decidí crear algunas imágenes para acompañar la explicación anterior. Quizás estos sean útiles
Así es como (esquemáticamente) se almacena en la memoria en su ejemplo. Destaqué las diferencias con los ciclos rojos (a mano alzada):
En realidad, eso es solo una aproximación porque los
int
objetos también son objetos de Python y CPython incluso reutiliza números enteros pequeños, por lo que una representación probablemente más precisa (aunque no tan legible) de los objetos en la memoria sería:Enlaces útiles:
tuple
estructura en el repositorio CPython para Python 2.7list
estructura en el repositorio CPython para Python 2.7int
estructura en el repositorio CPython para Python 2.7¡Tenga en cuenta que
__sizeof__
realmente no devuelve el tamaño "correcto"! Solo devuelve el tamaño de los valores almacenados. Sin embargo, cuando usasys.getsizeof
el resultado es diferente:Hay 24 bytes "extra". Estos son reales , esa es la sobrecarga del recolector de basura que no se tiene en cuenta en el
__sizeof__
método. Esto se debe a que generalmente no se supone que use métodos mágicos directamente; use las funciones que saben cómo manejarlos, en este caso:sys.getsizeof
(que en realidad agrega la sobrecarga de GC al valor devuelto__sizeof__
).fuente
list
la asignación de memoria stackoverflow.com/questions/40018398/…list()
o una lista de comprensión.Profundizaré en el código base de CPython para que podamos ver cómo se calculan realmente los tamaños. En su ejemplo específico , no se han realizado sobreasignaciones, así que no tocaré eso .
Voy a usar valores de 64 bits aquí, como tú.
El tamaño de
list
s se calcula a partir de la siguiente función,list_sizeof
:Aquí
Py_TYPE(self)
hay una macro que toma elob_type
deself
(regresandoPyList_Type
) mientras que_PyObject_SIZE
es otra macro que tomatp_basicsize
de ese tipo.tp_basicsize
se calcula comosizeof(PyListObject)
dóndePyListObject
está la estructura de la instancia.La
PyListObject
estructura tiene tres campos:estos tienen comentarios (que recorté) que explican qué son, siga el enlace de arriba para leerlos.
PyObject_VAR_HEAD
se expande en tres campos de 8 bytes (ob_refcount
,ob_type
yob_size
) por lo que es una24
contribución de bytes.Entonces por ahora
res
es:o:
Si la instancia de la lista tiene elementos asignados. la segunda parte calcula su contribución.
self->allocated
, como su nombre lo indica, contiene el número de elementos asignados.Sin ningún elemento, el tamaño de las listas se calcula como:
es decir, el tamaño de la estructura de la instancia.
tuple
los objetos no definen unatuple_sizeof
función. En cambio, usanobject_sizeof
para calcular su tamaño:Esto, en cuanto a
list
s, toma eltp_basicsize
y, si el objeto tiene un valor distinto de cerotp_itemsize
(lo que significa que tiene instancias de longitud variable), multiplica el número de elementos en la tupla (que obtiene a través dePy_SIZE
) contp_itemsize
.tp_basicsize
nuevamente usasizeof(PyTupleObject)
donde laPyTupleObject
estructura contiene :Entonces, sin ningún elemento (es decir,
Py_SIZE
devoluciones0
), el tamaño de las tuplas vacías es igual asizeof(PyTupleObject)
:eh Bueno, aquí hay una rareza para la que no he encontrado una explicación, la
tp_basicsize
detuple
s se calcula de la siguiente manera:por qué
8
se eliminan bytes adicionalestp_basicsize
es algo que no he podido averiguar. (Ver el comentario de MSeifert para una posible explicación)Pero, esta es básicamente la diferencia en su ejemplo específico .
list
s también mantienen una serie de elementos asignados que ayuda a determinar cuándo sobreasignar nuevamente.Ahora, cuando se agregan elementos adicionales, las listas sí realizan esta sobreasignación para lograr agregados O (1). Esto da como resultado tamaños más grandes, ya que MSeifert cubre muy bien en su respuesta.
fuente
ob_item[1]
es principalmente un marcador de posición (por lo que tiene sentido que se reste del tamaño básico). Eltuple
se asigna usandoPyObject_NewVar
. No he descubierto los detalles, así que es solo una suposiciónLa respuesta de MSeifert lo cubre ampliamente; para hacerlo simple, puede pensar en:
tuple
es inmutable. Una vez configurado, no puede cambiarlo. Entonces, sabe de antemano cuánta memoria necesita asignar para ese objeto.list
es mutable. Puede agregar o quitar elementos de él. Tiene que saber su tamaño (para impl. Interna). Cambia de tamaño según sea necesario.No hay comidas gratis ; estas capacidades tienen un costo. De ahí la sobrecarga en la memoria para las listas.
fuente
El tamaño de la tupla tiene un prefijo, lo que significa que en la inicialización de la tupla el intérprete asigna suficiente espacio para los datos contenidos, y ese es el final, lo que le da inmutable (no se puede modificar), mientras que una lista es un objeto mutable, por lo tanto, implica dinámica asignación de memoria, por lo que para evitar asignar espacio cada vez que agrega o modifica la lista (asigne suficiente espacio para contener los datos cambiados y copie los datos), asigna espacio adicional para futuras adiciones, modificaciones, ... eso prácticamente lo resume.
fuente