python - ¿Por qué el tamaño de 2⁶³ 36 bytes, pero 2⁶³-1 es solo de 24 bytes?
python-2.7 cpython (2)
Todo en Python es un objeto. Por lo tanto, el tamaño de un int en Python será mayor de lo habitual.
>>> sys.getsizeof(int())
24
Bien, pero ¿por qué se necesitan 12 bytes más para
2⁶³
comparación con
2⁶³ - 1
y no solo uno?
>>> sys.getsizeof(2**63)
36
>>> sys.getsizeof(2**62)
24
Entiendo que
2⁶³
es largo y
2⁶³-1
un int, pero ¿por qué 12 bytes de diferencia?
No más intuitivo, probé algunas otras cosas:
>>> a = 2**63
>>> a -= 2**62
>>> sys.getsizeof(a)
36
a
todavía se almacena como un largo, incluso si pudiera estar en un int ahora.
Entonces eso no es sorprendente.
Pero:
>>> a -= (2**63 - 1)
>>> a = 2**63
>>> a -= (2**63 - 1)
>>> a
1L
>>> sys.getsizeof(a)
28
Un nuevo tamaño.
>>> a = 2**63
>>> a -= 2**63
>>> a
0L
>>> sys.getsizeof(a)
24
Volver a 24 bytes, pero aún con un largo.
Lo último que obtuve:
>>> sys.getsizeof(long())
24
Pregunta:
¿Cómo funciona el almacenamiento de memoria en esos escenarios?
Sub-preguntas:
¿Por qué hay un espacio de 12 bytes para agregar lo que nuestra intuición nos dice que es solo 1 bit?
¿Por qué son
int()
y
long()
24 bytes, pero
long(1)
ya tiene 28 bytes e
int(2⁶²)
?
NB: Python 3.X funciona un poco diferente, pero no de manera más intuitiva. Aquí me concentré en Python 2.7; No probé en versiones anteriores.
¿por qué obtiene 12 bytes más para 2⁶³ en comparación con 2⁶³ - 1 y no solo uno?
En un sistema
LP64
1
, un Python 2
int
consta de
exactamente tres piezas del tamaño de un puntero:
- puntero de tipo
- recuento de referencia
-
valor real, una C
long int
Eso es 24 bytes en total.
Por otro lado, un Python
long
consiste en
:
- puntero de tipo
- recuento de referencia
- recuento de dígitos, un entero de tamaño puntero
- Matriz en línea de dígitos de valor, cada uno con 30 bits de valor, pero almacenados en unidades de 32 bits (uno de los bits no utilizados se utiliza para llevar / prestar eficientemente durante la suma y resta)
2 ** 63 requiere 64 bits para almacenar, por lo que cabe en tres dígitos de 30 bits.
Como cada dígito tiene 4 bytes de ancho, toda la
long
Python tomará 24 + 3 * 4 = 36 bytes.
En otras palabras, la diferencia proviene de tener que almacenar por separado el tamaño del número (8 bytes adicionales) y de ser un poco menos eficiente en cuanto al espacio para almacenar el valor (12 bytes para almacenar los dígitos de 2 ** 63).
Incluyendo el tamaño, el valor 2 ** 63 en un
long
ocupa 20 bytes.
Comparando eso con los 8 bytes ocupados por
cualquier
valor del
int
simple se obtiene la diferencia observada de 12 bytes.
Vale la pena señalar que Python 3 solo tiene un tipo entero, llamado
int
, que es de ancho variable, y se implementa de la misma manera que Python 2 de
long
.
1 Windows de 64 bits difiere en que conserva un
long int
32 bits de
long int
, presumiblemente por compatibilidad de fuente con un gran cuerpo de código antiguo que usaba alias
char
,
short
y
long
como "conveniente" para 8, 16 y 32 bits valores que funcionaron en sistemas de 16 y 32 bits.
Para obtener un tipo real de 64 bits en Windows x86-64, se debe usar
__int64
o (en las versiones más recientes del compilador)
long long
o
int64_t
.
Dado que Python 2 depende internamente de que Python
int
ajuste a una C larga en varios lugares,
sys.maxint
sigue siendo
2**31-1
, incluso en Windows de 64 bits.
Esta peculiaridad también se soluciona en Python 3, que no tiene el concepto de
maxint
.
Si bien no lo encontré en la documentación, aquí está mi explicación.
Python 2 promueve
int
a
long
implícitamente, cuando el valor excede el valor que se puede almacenar en int.
El tamaño del nuevo tipo (
long
) es el tamaño predeterminado de
long
, que es 32. A partir de ahora, el tamaño de su variable estará determinado por su valor, que puede subir y bajar.
from sys import getsizeof as size
a = 1
n = 32
# going up
for i in range(10):
if not i:
print ''a = %100s%13s%4s'' % (str(a), type(a), size(a))
else:
print ''a = %100s%14s%3s'' % (str(a), type(a), size(a))
a <<= n
# going down
for i in range(11):
print ''a = %100s%14s%3s'' % (str(a), type(a), size(a))
a >>= n
a = 1 <type ''int''> 24
a = 4294967296 <type ''long''> 32
a = 18446744073709551616 <type ''long''> 36
a = 79228162514264337593543950336 <type ''long''> 40
a = 340282366920938463463374607431768211456 <type ''long''> 44
a = 1461501637330902918203684832716283019655932542976 <type ''long''> 48
a = 6277101735386680763835789423207666416102355444464034512896 <type ''long''> 52
a = 26959946667150639794667015087019630673637144422540572481103610249216 <type ''long''> 56
a = 115792089237316195423570985008687907853269984665640564039457584007913129639936 <type ''long''> 60
a = 497323236409786642155382248146820840100456150797347717440463976893159497012533375533056 <type ''long''> 64
a = 2135987035920910082395021706169552114602704522356652769947041607822219725780640550022962086936576 <type ''long''> 68
a = 497323236409786642155382248146820840100456150797347717440463976893159497012533375533056 <type ''long''> 64
a = 115792089237316195423570985008687907853269984665640564039457584007913129639936 <type ''long''> 60
a = 26959946667150639794667015087019630673637144422540572481103610249216 <type ''long''> 56
a = 6277101735386680763835789423207666416102355444464034512896 <type ''long''> 52
a = 1461501637330902918203684832716283019655932542976 <type ''long''> 48
a = 340282366920938463463374607431768211456 <type ''long''> 44
a = 79228162514264337593543950336 <type ''long''> 40
a = 18446744073709551616 <type ''long''> 36
a = 4294967296 <type ''long''> 32
a = 1 <type ''long''> 28
Como puede ver, el tipo permanece
long
después de que se hizo demasiado grande para un
int
, y el tamaño inicial era 32, pero el tamaño cambia con el valor (puede ser mayor o menor [o igual, obviamente] a 32)
Entonces, para responder a su pregunta, el tamaño base es 24 para
int
y 28 para
long
, mientras que
long
también tiene el espacio para guardar valores grandes (que comienzan como 4 bytes, por lo tanto, 32 bytes para
long
, pero pueden subir y bajar según al valor)
En cuanto a su subpregunta, es imposible crear un tipo único (con un tamaño único) para un nuevo número, por lo que Python tiene "subclases" de tipo
long
, que tratan con un rango de números, por lo tanto, una vez que supera el límite de su antiguo
long
debe usar el más nuevo, que también representa números mucho más grandes, por lo tanto, tiene algunos bytes más.