memory alignment memory-alignment

memory - Propósito de la alineación de la memoria



alignment memory-alignment (8)

Es cierto que no lo entiendo. Supongamos que tiene una memoria con una palabra de memoria de 1 byte de longitud. ¿Por qué no se puede acceder a una variable de 4 bytes de longitud en un solo acceso de memoria en una dirección no alineada (es decir, no divisible por 4), como es el caso con las direcciones alineadas?


@joshperry ha dado una excelente respuesta a esta pregunta. Además de su respuesta, tengo algunos números que muestran gráficamente los efectos que se describieron, especialmente la amplificación 2X. Aquí hay un enlace a una hoja de cálculo de Google que muestra cómo se ve el efecto de diferentes alineaciones de palabras. Además, aquí hay un enlace a un github gist con el código para la prueba. El código de prueba está adaptado del artículo escrito por Jonathan Rentzsch al que se hace referencia en @joshperry. Las pruebas se realizaron en un Macbook Pro con un procesador Intel Core i7 de cuatro núcleos a 2,8 GHz de 2,8 GHz y 16 GB de RAM.


El subsistema de memoria en un procesador moderno está restringido para acceder a la memoria en la granularidad y alineación de su tamaño de palabra; este es el caso por varias razones.

Velocidad

Los procesadores modernos tienen múltiples niveles de memoria caché que los datos deben ser procesados; admitir lecturas de un solo byte haría que el rendimiento del subsistema de memoria se vincule estrechamente al rendimiento de la unidad de ejecución (también conocido como cpu-bound); todo esto es una reminiscencia de cómo el modo PIO fue superado por DMA por muchas de las mismas razones en los discos duros.

La CPU siempre lee en su tamaño de palabra (4 bytes en un procesador de 32 bits), por lo que cuando acceda a una dirección no alineada, en un procesador que la admita, el procesador leerá varias palabras. La CPU leerá cada palabra de memoria en la que se encuentra la dirección solicitada. Esto provoca una amplificación de hasta 2 veces la cantidad de transacciones de memoria requeridas para acceder a los datos solicitados.

Debido a esto, puede ser más fácil leer dos bytes que cuatro. Por ejemplo, supongamos que tienes una estructura en la memoria que se ve así:

struct mystruct { char c; // one byte int i; // four bytes short s; // two bytes }

En un procesador de 32 bits, lo más probable es que esté alineado como se muestra aquí:

El procesador puede leer cada uno de estos miembros en una transacción.

Digamos que tenía una versión empaquetada de la estructura, tal vez de la red donde estaba empaquetada para la eficiencia de la transmisión; podría ser algo como esto:

Leer el primer byte va a ser el mismo.

Cuando solicite al procesador que le proporcione 16 bits de 0x0005, deberá leer una palabra de 0x0004 y desplazarse a la izquierda 1 byte para colocarla en un registro de 16 bits; un poco de trabajo extra, pero la mayoría puede manejar eso en un ciclo.

Cuando solicite 32 bits desde 0x0001 obtendrá una amplificación 2X. El procesador leerá de 0x0000 en el registro de resultados y desplazará 1 byte de la izquierda, luego volverá a leer de 0x0004 a un registro temporal, desplazará 3 bytes a la derecha, luego OR lo hará con el registro de resultados.

Distancia

Para cualquier espacio de direcciones dado, si la arquitectura puede suponer que los 2 LSB son siempre 0 (por ejemplo, máquinas de 32 bits), entonces puede acceder a 4 veces más memoria (los 2 bits guardados pueden representar 4 estados distintos), o la misma cantidad de memoria con 2 bits para algo así como banderas. Quitar los 2 LSB de una dirección le daría una alineación de 4 bytes; también se conoce como una stride de 4 bytes. Cada vez que se incrementa una dirección, efectivamente se incrementa el bit 2, no el bit 0, es decir, los últimos 2 bits siempre continuarán siendo 00 .

Esto incluso puede afectar el diseño físico del sistema. Si el bus de direcciones necesita 2 bits menos, puede haber 2 pines menos en la CPU y 2 trazas menos en la placa de circuito.

Atomicidad

La CPU puede operar en una palabra de memoria alineada de forma atómica, lo que significa que ninguna otra instrucción puede interrumpir esa operación. Esto es fundamental para el correcto funcionamiento de muchas estructuras de datos sin bloqueo y otros paradigmas de concurrency .

Conclusión

El sistema de memoria de un procesador es bastante más complejo e involucrado que el descrito aquí; una discusión sobre cómo un procesador x86 realmente aborda la memoria puede ayudar (muchos procesadores funcionan de manera similar).

Hay muchos más beneficios para adherirse a la alineación de memoria que puede leer en this .

El uso principal de una computadora es transformar datos. Las arquitecturas y tecnologías de memoria modernas se han optimizado durante décadas para facilitar la obtención de más datos, dentro, fuera, y entre más y unidades de ejecución más rápidas, de una manera altamente confiable.

Bonificación: cachés

Otra alineación para el rendimiento a la que aludí anteriormente es la alineación en líneas de caché que son (por ejemplo, en algunas CPU) 64B.

Para obtener más información sobre cuánto rendimiento se puede obtener aprovechando los cachés, consulte Galería de efectos de caché de procesador ; de esta pregunta en los tamaños de la línea de caché

La comprensión de las líneas de caché puede ser importante para ciertos tipos de optimizaciones de programas. Por ejemplo, la alineación de datos puede determinar si una operación toca una o dos líneas de caché. Como vimos en el ejemplo anterior, esto puede significar fácilmente que en el caso desalineado, la operación será dos veces más lenta.


En PowerPC puede cargar un número entero desde una dirección impar sin problemas.

Sparc e I86 y (creo) Itatnium levantan excepciones de hardware cuando intentas esto.

Una carga de 32 bits frente a cuatro cargas de 8 bits no supondrá una gran diferencia en la mayoría de los procesadores modernos. Si los datos ya están en caché o no tendrá un efecto mucho mayor.


Es una limitación de muchos procesadores subyacentes. Por lo general, se puede solucionar haciendo 4 búsquedas inequívocas de bytes individuales en lugar de una búsqueda de palabras eficiente, pero muchos especificadores de lenguaje decidieron que sería más fácil simplemente proscribirlos y obligar a que todo se alinee.

Hay mucha más información en this que el OP descubrió.


Fundamentalmente, la razón es porque el bus de memoria tiene una longitud específica que es mucho, mucho más pequeña que el tamaño de la memoria.

Por lo tanto, la CPU lee de la memoria caché L1 en el chip, que a menudo es de 32 KB en estos días. Pero el bus de memoria que conecta la caché L1 a la CPU tendrá el ancho mucho menor del tamaño de la línea de caché. Esto será del orden de 128 bits .

Asi que:

262,144 bits - size of memory 128 bits - size of bus

Los accesos desalineados ocasionalmente se superpondrán a dos líneas de caché, y esto requerirá una lectura de caché completamente nueva para obtener los datos. Incluso podría perderse todo el camino hacia la DRAM.

Además, una parte de la CPU tendrá que ponerse de cabeza para formar un solo objeto a partir de estas dos líneas de caché diferentes, cada una de las cuales tiene una parte de los datos. En una línea, estará en los bits de orden muy alto, en el otro, los bits de orden muy bajo.

Habrá hardware dedicado completamente integrado en la tubería que maneja objetos alineados en movimiento en los bits necesarios del bus de datos de la CPU, pero tal hardware puede faltar para objetos mal alineados, porque probablemente tenga más sentido usar esos transistores para acelerar correctamente optimizados programas.

En cualquier caso, la segunda lectura de memoria que a veces es necesaria ralentizaría la tubería sin importar cuánto hardware de propósito especial se haya dedicado (hipotéticamente y tontamente) a reparar las operaciones de memoria desalineadas.


Si tiene un bus de datos de 32 bits, las líneas de dirección del bus de direcciones conectadas a la memoria comenzarán desde A 2 , de modo que solo se puede acceder a las direcciones alineadas a 32 bits en un solo ciclo de bus.

Entonces, si una palabra abarca un límite de alineación de direcciones, es decir, A 0 para datos de 16/32 bits o A 1 para datos de 32 bits no son cero, se requieren dos ciclos de bus para obtener los datos.

Algunas arquitecturas / conjuntos de instrucciones no admiten el acceso no alineado y generarán una excepción en dichos intentos, por lo que el código de acceso no alineado generado por el compilador requiere no solo ciclos de bus adicionales, sino también instrucciones adicionales, lo que lo hace aún menos eficiente.


Si un sistema con memoria direccionable por byte tiene un bus de memoria de 32 bits de ancho, eso significa que efectivamente hay cuatro sistemas de memoria de byte de ancho que están todos conectados para leer o escribir la misma dirección. Una lectura alineada de 32 bits requerirá información almacenada en la misma dirección en los cuatro sistemas de memoria, por lo que todos los sistemas pueden suministrar datos simultáneamente. Una lectura desalineada de 32 bits requeriría que algunos sistemas de memoria devuelvan datos desde una dirección, y algunos que devuelvan datos desde la siguiente dirección más alta. Aunque hay algunos sistemas de memoria que están optimizados para poder cumplir con dichas solicitudes (además de su dirección, tienen efectivamente una señal "más uno" que les hace usar una dirección más alta que la especificada) dicha característica agrega un costo considerable y complejidad a un sistema de memoria; la mayoría de los sistemas de memoria básicos simplemente no pueden devolver partes de diferentes palabras de 32 bits al mismo tiempo.


puedes hacerlo con algunos procesadores ( el nehalem puede hacerlo ), pero anteriormente todo el acceso a la memoria estaba alineado en una línea de 64 bits (o 32 bits), porque el bus tiene 64 bits de ancho, tienes que buscar 64 bits a la vez , y fue significativamente más fácil buscarlos en ''fragmentos'' alineados de 64 bits.

Entonces, si quieres obtener un solo byte, has buscado el fragmento de 64 bits y luego has ocultado los bits que no querías. Fácil y rápido si su byte estaba en el extremo derecho, pero si estaba en el medio de ese fragmento de 64 bits, tendría que enmascarar los bits no deseados y luego desplazar los datos al lugar correcto. Peor aún, si quería una variable de 2 bytes, pero estaba dividida en 2 segmentos, entonces requería el doble de los accesos de memoria requeridos.

Entonces, como todos piensan que la memoria es barata, simplemente hicieron que el compilador alineara los datos en los tamaños de los fragmentos del procesador para que tu código funcione más rápido y de manera más eficiente a costa de la pérdida de memoria.