python python-2.7 cpython python-internals

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.