La memoria real de un array en C, C++, Java, Python, JavaScript o C#, con la sobrecarga de referencias y las cabeceras por fila, y cómo leer su longitud.
La expresión que usa cada lenguaje, y si es un campo, un método o una función libre.
| Lenguaje | Expresión |
|---|---|
| C | sizeof(arr) / sizeof(arr[0]) |
| C++ | std::size(arr) |
| Java | arr.length |
| Python | len(arr) |
| JavaScript | arr.length |
| C# | arr.Length |
Bytes por elemento en una compilación de 64 bits. La columna de Python es el tamaño del objeto encapsulado, no de una ranura.
| Tipo | C / C++ | Java | C# | Python |
|---|---|---|---|---|
byte | 1 | 1 | 1 | 1 |
char | 1 | 2 | 2 | 1 |
short | 2 | 2 | 2 | 2 |
int | 4 | 4 | 4 | 28 |
long | 8 | 8 | 8 | 28 |
float | 4 | 4 | 4 | 24 |
double | 8 | 8 | 8 | 24 |
boolean | 1 | 1 | 1 | 28 |
pointer | 8 | 8 | 8 | 8 |
Bytes de cabecera por contenedor, si las ranuras guardan valores o referencias, y cómo se dispone un array multidimensional. El asterisco marca un detalle interno del entorno de ejecución, no un tamaño especificado.
| Lenguaje | Cabecera | Las ranuras guardan | Disposición 2-D |
|---|---|---|---|
| C | 0 | Valores almacenados en línea | Un bloque contiguo |
| C++ | 0 | Valores almacenados en línea | Un bloque contiguo |
| Java | 16 | Valores almacenados en línea | Array de arrays por fila |
| Python | 56 * | Referencias a objetos encapsulados | Array de arrays por fila |
| JavaScript | 32 * | Valores almacenados en línea | Array de arrays por fila |
| C# | 24 | Valores almacenados en línea | Un bloque contiguo |
Los tamaños de los elementos siguen la especificación de cada lenguaje cuando existe. Los tamaños de cabecera de los contenedores son detalles del entorno de ejecución: los de Java y C# son estables entre las implementaciones habituales, mientras que la cabecera de las listas de CPython y la representación de arrays de V8 son detalles internos que pueden cambiar entre versiones y se señalan como tales. Toma las cifras como un modelo aproximado de una compilación de 64 bits, no como una garantía para un entorno concreto.
También podrías encontrar útiles estas calculadoras
Pega tu código y obtén su Big-O de tiempo y espacio
Calcula la RAM óptima para tu PC, estación de trabajo o servidor
Mide bytes JSON y comprueba límites de plataforma
Convierte entre unidades de almacenamiento digital
Hay dos preguntas que traen a la gente aquí, y tienen respuestas distintas. La primera es cuánta memoria ocupa un array, que depende de mucho más que del número de elementos, porque una matriz de Java lleva una cabecera por fila y una lista de Python guarda referencias en lugar de valores. La segunda es simplemente cómo leer la longitud en un lenguaje concreto, donde la respuesta es un campo en uno, un método en otro, y en C un truco aritmético que se rompe en cuanto el array se pasa a una función. Esta página responde a las dos, y muestra la aritmética en lugar de solo el total.
Un array plano de tipos primitivos es el caso fácil: multiplica el tamaño del elemento por el número de elementos y suma la cabecera del contenedor. Todo lo demás se aparta de eso. Con semántica de referencias — una lista de Python, o cualquier array de objetos encapsulados — cada ranura guarda un puntero de 8 bytes y el valor vive en un objeto aparte, así que hay dos totales honestos: el contenedor por sí solo, que es lo que informa sys.getsizeof, y el total profundo que sigue las referencias. En un lenguaje con arrays multidimensionales escalonados, int[500][100] es un array externo de 500 referencias más 500 arrays internos, cada uno con su cabecera: unos 212.016 bytes en Java, donde un producto plano diría 200.016. Ambas distinciones marcan la diferencia entre una estimación que se sostiene y otra que falla por varios puntos porcentuales sin avisar.
Los dos modelos
Decidir si diez millones de registros caben en memoria exige el coste real por elemento, cabeceras y referencias incluidas, y no solo el tamaño del elemento.
Comparar una lista de Python con array.array o numpy, o un array de JavaScript con un TypedArray, es una decisión de memoria antes que de velocidad, y la diferencia suele ser mayor de lo que se espera.
El truco sizeof(arr)/sizeof(arr[0]) funciona en el ámbito donde se declara el array y devuelve silenciosamente la respuesta equivocada dentro de una función. Ver por qué vale más que memorizar la expresión.
Cuando un perfilador informa de varias veces la memoria que esperabas, las cabeceras por fila y los elementos encapsulados son normalmente el destino.
Una lista de Python con 500 enteros no contiene 500 enteros. Contiene 500 punteros de 8 bytes cada uno, y los objetos entero están en otro sitio, a unos 28 bytes cada uno. Multiplicar 28 por 500 no describe ni el contenedor ni el total, que es exactamente el error que cometía antes esta página.
Los arrays escalonados de Java y C#, las listas de listas de Python y los arrays de arrays de JavaScript reservan un contenedor por fila. Con 500 filas eso son 500 cabeceras extra, y es la razón de que la misma declaración cueste bastante más en Java que en C.
CPython guarda en caché los enteros del -5 al 256, así que una lista de números pequeños comparte esos objetos y cuesta más cerca de la cifra del contenedor que del total profundo. Cualquier herramienta que dé un único número sin decir cuál está ocultando un factor de cuatro.
La cabecera de objeto de Java y la de array de C# son estables entre los entornos habituales. La cabecera de las listas de CPython y la representación de arrays de V8 son detalles internos. Dar una cifra con dos cifras significativas para V8 implica una precisión que la plataforma no ofrece, así que esta página lo señala en lugar de disimularlo.
Usa sizeof(arr) / sizeof(arr[0]), pero solo en el ámbito donde arr se declara como array. Si lo pasas a una función como int arr[] o int *arr, el array degenera en un puntero, así que sizeof(arr) devuelve el tamaño del puntero, 8 en una compilación de 64 bits, y la división devuelve 2 para un array de int en lugar de su longitud real. La longitud no forma parte del tipo de un puntero, así que tiene que viajar junto a él como parámetro aparte, o usas std::array o std::vector en C++.
Java usa arr.length como campo, sin paréntesis, mientras que una colección usa .size() y un String usa .length(). Python usa len(arr). JavaScript usa arr.length, que se puede escribir: arr.length = 0 vacía el array. C# usa arr.Length para el total, GetLength(0) por dimensión de un array rectangular, y Count para un List<T>. Esas tres formas de escribir una misma idea son la razón de que se busque tanto.
Porque la pregunta tiene dos respuestas. Una lista de 500 enteros cuesta unos 4056 bytes como contenedor — 56 del objeto lista más 500 punteros de ocho bytes, que es lo que devuelve sys.getsizeof — y unos 18.056 si cuentas los objetos entero a los que apunta. Ninguna cifra es incorrecta; responden a preguntas distintas, y dar solo una oculta un factor de cuatro.
No. len() cuenta elementos. sys.getsizeof(arr) da el tamaño del propio contenedor y deliberadamente no sigue las referencias, así que los objetos que contiene no se incluyen. Sumarlos es lo que hace aquí el total profundo.
Porque son formas distintas. En C la declaración es un único bloque contiguo de 50.000 int sin cabecera alguna: 200.000 bytes. En Java es un array de 500 referencias a 500 arrays int[100] separados, así que pagas el array externo más 500 cabeceras internas: unos 212.016 bytes. Los 12.016 de diferencia son la sobrecarga por fila.
Mucho menos de lo que sugiere el total profundo si los números son pequeños. CPython guarda en caché los enteros del -5 al 256, así que una lista de valores pequeños comparte esos objetos y cuesta cerca de la cifra del contenedor. Los valores grandes o distintos sí se reservan individualmente. Si necesitas un almacenamiento compacto y predecible, array.array('i') guarda los mismos enteros en línea a 4 bytes cada uno, y numpy aún menos.
Los tamaños de los elementos siguen la especificación de cada lenguaje cuando existe y son exactos. Las cabeceras de los contenedores son detalles del entorno de ejecución: las de Java y C# son estables entre las implementaciones habituales, mientras que la cabecera de las listas de CPython y la representación de arrays de V8 son detalles internos que pueden cambiar entre versiones, y esta página los señala en lugar de dar por hecho una precisión que no tienen.