x64 vs x86: ¿Por qué un programa de 64 bits ocupa casi el doble de memoria que uno de 32?

Esta entrada tiene su origen en una pregunta que hizo Jume en el grupo de Generales del servidor de Tella sobre el título; a ella dieron cabal respuesta el propio Tella y José Antonio Quílez, aunque de forma bastante resumida, lo que fue motivo para que el que suscribe iniciara una disertación sobre el tema. Finalmente, gracias al empujón de Ramón Sola, y a la depuración de ideas que el propio hilo ha generado, he decidido poner una entrada aquí sobre el tema.

¿Por qué un programa de 64 bits ocupa casi el doble de memoria que uno de 32 si el código fuente es el mismo?

En 32 bits la frontera de RAM son 4 bytes, es decir, un salto «nativo» de dirección salta 4 bytes, de, por ejemplo, 16 a 20. Puede saltar de 16 a 17 sin problemas, pero eso requiere ciertos ajustes internos del microprocesador y de la RAM (dado que las RAM modernas también están vivas) que llevan un importante consumo de tiempo. Es como si en tu casa pasaras corriendo por un pasillo que tuviera puertas a los lados, y que una de cada 4 estuviera siempre delante. Si pasas de 4 en 4, sólo corres en línea recta. Si pasas de 2 en 2, tienes que frenar en la segunda, girar, y volver a acelerar. Y encima, la RAM (la puerta), está cerrada si no es la de enfrente, por lo que también has de esperar a que se abra. Trasladándolo al micro, es más fácil y rápido leer una ráfaga de 4 bytes de un golpe que 1 lectura de 1, aunque de esos 4 bytes sólo vayas a usar 1.

En 64 bits ocurre lo mismo, pero ahora son 8 puertas en lugar de 4. Y aunque necesites 1, es más rápido leer 8 y descartar 7 que hacer todos los cambios para poder acceder fuera de la frontera de 8 bytes, leer 1 y luego deshacer los cambios para volver al estado anterior.

Lo que hacen los compiladores es optimizar esos accesos, de forma que no siempre se necesita 1 y se toman 8 (si no un programa de 64 bits gastaría justo el doble de memoria que uno de 32), a veces se necesitan 4, o 5, o 6 o 300. En el último caso, 300mod8 es 4, nos faltan 4 bytes en 64 bits y 0 en 32, por lo que en 64 bits tenemos que coger 304 bytes, desaprovechado 4 bytes de memoria, con lo que estamos gastando 4 bytes más en 64 que en 32. Este es uno de los factores que hacen que un programa de 64 bits gaste más memoria que uno de 32.

Luego están los punteros. Los punteros, aunque a nadie le gustan (sin embargo, a mi me encantan), son la vida de un ordenador. Si no se hubiera inventado tanto su concepto como su implementación, no existirían los ordenadores. Un puntero es una variable situada en memoria que guarda la dirección de otro bloque de memoria.

En el caso anterior, y para 32 bits, hemos asignado 300 bytes de RAM. Tenemos por un lado los 300 bytes en un hueco de la memoria, y por otro tenemos 4 bytes en nuestro programa que apuntan a la dirección base de esos 300 bytes. Cuando nuestro programa necesita algo de esos 300, se va al puntero, mira su contenido y, si quiere acceder a la posición 4 dentro de esos 300, suma 4 al valor del puntero y lee en la dirección física de destino (y esas lecturas serán mucho más rápidas si están alienadas a 4 bytes que si no lo están).

En 64 bits, ese puntero ocupa 8 bytes en lugar de 4, el bloque de 300 es de 304, y el proceso de funcionamiento es idéntico. Y ese puntero ocupa 8 bytes no porque la memoria se lea de 8 en 8, sino porque mientras que la arquitectura de 32 bits necesita 4 bytes para guardar todas las direcciones posibles, la de 64 necesita 8 para hacer lo mismo y, por tanto, nuestros punteros tienen que tener ese tamaño.

Por lo tanto, en 64 bits cada puntero ocupa el doble que en 32 bits. Si una aplicación usa 123 punteros en un momento dado, esos punteros, alineación aparte (porque su propio «hueco» en la memoria también está alineado a 4 u 8 bytes), ocuparán 123×4 en 32 bits y 123×8 en 64 bits.
El hecho de que la alienación y los punteros sean iguales entre sí tiene que ver con la propia arquitectura física de la máquina; la idea básica es que se producen ciertas optimizaciones dentro de la construcción del hardware si la alineación de datos se produce con el mismo tamaño que el del ancho de la dirección. Para hacernos una idea, es como si un microprocesador y una memoria estuvieran construidos con muchos bloques predefinidos que tuvieran 4 u 8 puertas respectivamente de su arquitectura, con dos de ellas una enfrente de la otra. Y en algunos casos, hasta se ahorran pistas sobre el circuito impreso al no cablear las dos o cuatro líneas de dirección más bajas (Addr0 y Addr1, Addr2 y Addr3).

Pero el tema no es tan simple, ya que luego ese puntero de 4/8 bytes apunta realmente al final de una estructura cuyos valores siguientes son el bloque de memoria asignado y cuyos valores anteriores son otros números que Windows/Linux necesitan para gestionar la memoria, ya que de hecho la memoria se asigna habitualmente en bloques de 4K y luego el runtime correspondiente genera una lista enlazada que reparte esa memoria en los bloques pedidos (con lo que todavía hay más punteros), y a su vez esas direcciones son relativas y móviles dentro de la memoria física, de forma que el kernel sea capaz de moverlos en la memoria física (todavía más punteros), de modo que al final el valor medio de aumentos es de entre un 40% y el doble, lo que no deja de ser una tasa completamente estimativa.

En el tema de los punteros también intervienen más factores, como lo inteligente que sea el compilador sustituyendo datos estáticos por su valor directamente, los algoritmos que el programador haya decidido implementar, cuántos bloques de memoria se asignen y cómo, y la propia gestión de memoria tanto del Kernel como del runtime, amén del grado de fragmentación de la misma.

Como ejemplo típico está el uso de un array disperso, que es un array que crece en bloques. Imaginemos un grano de 16, cuando el array tenga 16 elementos y pase a 17, se vuelve a crear un nuevo bloque de 16 elementos y se ocupa uno. Hemos añadido al menos tres punteros más al sistema (aparte de los del gestor de memoria). Supongamos que ese array disperso sea un array de punteros…Casi nada, la de punteros que hemos creado… Y una aplicación más o menos seria (Word, IE, Outlook) muy bien podría tener varios miles de ellos…

También está el tema del tamaño de los datos, aunque aquí las especificaciones sí que han sido un poco comedidas. Pensemos que una variable de tipo bool ocupa en una arquitectura de 32 bits, 4 bytes, y 8 en una de 64. Tengamos un array disperso de booleanos y nos daremos cuenta de cómo crece el gasto de memoria.

Luego está el tema del juego de instrucciones, en x64 está más optimizado y hay más que en x86… y, no es lo mismo un programa compilado en 32 bits que en 64. En general en 64 bits se generan menos instrucciones y estas son más rápidas… con lo que la relación de tamaños de un programa guardado en disco no es lineal. Quizás los datos guardados o fijos dentro del ejecutable ocupen justo el doble, pero el código, al ser menor, hace que ocupen algo menos, pero como los códigos de operación de 64 bits son más largos que los 32 bits, el tema se complica hasta límites insospechados.

Pongamos un ejemplo práctico: mi zxDelTemp, que tiene exactamente el mismo código fuente MFC tanto en su versión de 32 como de 64 bits. La compilación que uso para 32 bits ocupa 237.680 bytes, mientras que la de 64 bits ocupa 476.672 bytes, aproximadamente un 68,7% más.

SysWoW64

Nos queda el tema del SysWoW64, es decir, tener un programa de 32 bits ejecutándose dentro de un entorno de 64. Aunque nunca lo he mirado en detalle, tengo unas cuantas cosas claras, ya que son completamente lógicas.

Windows suministra un entorno virtual de 32 bits sobre uno de 64, es decir, las aplicaciones de 32 bits se ejecutan en un pseudo código de 32 bits, pero el núcleo es de 64 bits, por lo que cuando una aplicación pide un recurso al sistema (abrir un fichero, por ejemplo), Windows crea una serie de estructuras (completamente llenas de punteros a muchos sitios), estructura que es pasada al subsistema de 32 bits convertida a su tamaño (con lo que duplicamos la misma estructura, una de 64 y otra de 32, y si no la duplicamos –realmente no lo tengo claro-, estamos al menos usando una estructura de 64 bits –ya sabemos, alineada a 8, con punteros de 8 bytes, etc.-, en un sistema de 32).

Cuando un programa de 32 bits reserva memoria, Windows ha de asignar un bloque con formato de 64 bits aunque luego dentro del wow64 haya un subgestor de memoria local, pero el hecho es que también estamos trasteando con punteros de 64 bits…

Finalmente, y aunque la aplicación se esté ejecutando en un entorno de 32 bits, todas las estructuras del núcleo asociadas a dicha aplicación son de 64 bits, amén de que seguro algunas (si no todas) se encuentran duplicadas dentro del subsistema de 32 bits, porque no se entiende cómo un programa que espera un puntero de 4 bytes ha de poder procesar uno de 8.

Por lo tanto, las aplicaciones de 32 bits corriendo dentro de un sistema de 64, también consumen más memoria que si lo estuvieran en uno de 32; ciertamente en muchos casos se ejecutan sensiblemente más rápido (por ejemplo SQL Server), pero esto se debe a que se produce menos fragmentación de memora, hay más disponible y por tanto se asigna más rápidamente, y las instrucciones de 32 bits dentro de un x64 se ejecutan mucho más rápidamente que bajo un x86.

Resumen

Resumiendo, el código x64 resulta de mayor tamaño tanto en disco como en memoria debido a los siguientes factores (quizá se me escape alguno):

· El juego de instrucciones de x64 ocupa el más que el de x86.

· Los punteros ocupan justo el doble.

· La alineación de memoria es a 8 bytes en lugar de a 4.

· Muchos datos tienen un tamaño doble, pero no todos.

· Los metadatos también ocupan más.

9 comentarios sobre “x64 vs x86: ¿Por qué un programa de 64 bits ocupa casi el doble de memoria que uno de 32?”

  1. interesante pero te falto agregar que en windows 64 bits se usa el modelo de datos llp64 por lo que no todos los tipos de datos ocupan el doble de memoria.

  2. Eltincho, sí que lo digo, pero de forma no técnica y un poco indirecta: «También está el tema del tamaño de los datos, aunque aquí las especificaciones sí que han sido un poco comedidas» (Intento decir que no todos son el doble… pero realmente no está claro. Añadiré una frase más).

    Jorge, Elías, Eltincho, gracias por los comentarios (y a los que vengan después).

    🙂

  3. entonces que me recomendarias instalar el win 7 x64 o el x86 para aprovechar al maximo mi pc tengo 4bg de ram y micro de 4 nucleos de 2.6 ghz y 500 gb de disco duro de antemano gracias

  4. Pues estás junto en el límite de indecisión. Si tu placa en x86 te detecta menos de 3GB y tienes todos los drivers para x64, esta última, pero si no, x86…

  5. todo muy claro, y como siempre esta el «pero».
    Tengo un x86 ejecutandose dentro de un x 64 con una memoria de 4 gb.y mi placa me detecta 2.84 gb, Existe algún truco para dejar el x86 y que ocupe los 4 gb? o en defintiva tengo que instalarle el x 64?
    Gracias por compartir..

  6. No existe ningún truco, y aunque por ejemplo la versión estándar de Windows Server x86 detecta y usa los 4GB, en los windows cliente es imposible. (Bueno, es posible pero hay que cambiar el certificado interno del núcleo por otro hackeado y yo al menos no me fiaría un pelo).

    Si quieres usar los 4GB, no tendrás más narices que instalar un x64. Además, a fecha de hoy 2.8 son pocos GB para un x86…

  7. el x86 en W7 usa 3.25G Ram max los otros son pra el sistema y es muy muy util y estable aunque no lo represente que no este disponiblo no queire decir que no este funcinando en tu caso es emulado estas usando parte de la ram ,para cada OS x64 es buenisimo aunque es mejor usarlo desde 6G ram ejemplo x86 pide 1G ram min todos sabemos que de jala mejor con 2G ya que es uno para sistema y otro para nosostros perro 1G no nos alcansa esi que nesitamos 2 para nosotros devesmos tener 3 instalados para poder usar todo lo que nesesitamos sin que acuda a la virtual w7 savi esto y de 4 de ram nos da 3 y lo otro? para manejo del sistemas osea menos uso de la virtual y reserva coas Audio y otros perifericos asi que nos quedan 2.5 de ram real o libre para nosostros asi pasa en x64 solo que al doble osea 6 o hasta 8 ¡¿y los 16? no soy pregramador no trabajo con photoshop ni afterE asi que no creo que alguna ves yege a usas 16 XD

Responder a rfog Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *