Propósito de la alineación de la memoria.

196

Es cierto que no lo entiendo. Digamos que tiene una memoria con una palabra de memoria de longitud de 1 byte. ¿Por qué no 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 de las direcciones alineadas?

arca
fuente
17
Después de hacer algunas adicional googlear me encontré con este gran eslabón, que explica el problema muy bien.
arca
Echa un vistazo a este pequeño artículo para las personas que comienzan a aprender esto: blog.virtualmethodstudio.com/2017/03/memory-alignment-run-fools
darkgaze
3
Enlace @ark roto
John Jiang
2
@JohnJiang Creo que encontré el nuevo enlace aquí: developer.ibm.com/technologies/systems/articles/pa-dalign
ejohnso49

Respuestas:

63

Es una limitación de muchos procesadores subyacentes. Por lo general, se puede solucionar haciendo 4 recuperaciones ineficientes de un solo byte en lugar de una búsqueda eficiente de palabras, pero muchos especificadores de lenguaje decidieron que sería más fácil prohibirlas y forzar que todo esté alineado.

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

Paul Tomblin
fuente
311

El subsistema de memoria en un procesador moderno está restringido a 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é por los que deben extraerse los datos; admitir lecturas de un solo byte haría que el rendimiento del subsistema de memoria esté estrechamente vinculado al rendimiento de la unidad de ejecución (también conocido como enlazado a la CPU); Todo esto recuerda cómo DMA superó el modo PIO 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 realiza un acceso de dirección no alineado, en un procesador que lo admite, el procesador leerá varias palabras. La CPU leerá cada palabra de memoria que se encuentre a horcajadas sobre su dirección solicitada. Esto provoca una amplificación de hasta 2 veces el número de transacciones de memoria requeridas para acceder a los datos solicitados.

Debido a esto, puede ser mucho más lento leer dos bytes que cuatro. Por ejemplo, supongamos que tiene 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í:

Diseño de estructura

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 transmisión; podría verse más o menos así:

Estructura empacada

Leer el primer byte va a ser lo mismo.

Cuando le pida al procesador que le dé 16 bits de 0x0005, tendrá que leer una palabra de 0x0004 y desplazar 1 byte a la izquierda para colocarlo en un registro de 16 bits; algo de trabajo extra, pero la mayoría puede manejar eso en un ciclo.

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

Rango

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 un paso de 4 bytes. Cada vez que se incrementa una dirección, está incrementando efectivamente el bit 2, no el bit 0, es decir, los últimos 2 bits siempre seguirá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 atómicamente una palabra de memoria alineada, lo que significa que ninguna otra instrucción puede interrumpir esa operación. Esto es crítico para el funcionamiento correcto de muchas estructuras de datos sin bloqueo y otros paradigmas de concurrencia .

Conclusión

El sistema de memoria de un procesador es bastante más complejo e implicado 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 al adherirse a la alineación de la memoria que puede leer en este artículo de IBM .

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, entrada, salida y entre más unidades de ejecución más rápidas, de una manera altamente confiable.

Bonus: cachés

Otra alineación por 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 la cantidad de rendimiento que se puede obtener aprovechando los cachés, eche un vistazo a la Galería de efectos de caché del procesador ; de esta pregunta sobre tamaños de 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.

joshperry
fuente
Las siguientes estructuras xyz tienen diferentes tamaños, debido a que la regla de cada miembro tiene que comenzar con la dirección que es múltiplos de su tamaño y el strup debe terminar con la dirección que es múltiplos del tamaño más grande del miembro de la estructura. struct x {short s; // 2 bytes y 2 tipos de relleno int i; // 4 bytes char c; // 1 bytes y 3 bytes de relleno largo largo l; }; struct y {int i; // 4 bytes char c; // 1 bytes y 1 byte de relleno short s; // 2 bytes}; struct z {int i; // 4 bytes cortos s; // 2 bytes char c; // 1 bytes y 1 byte de relleno};
Gavin
1
Si entiendo correctamente, ¿por qué una computadora no puede leer una palabra no alineada en un solo paso es porque las direcciones usan 30 bits y no 32 bits?
GetFree
1
@chux Sí, es verdad, los absolutos nunca se cumplen. El 8088 es un estudio interesante de las compensaciones entre velocidad y costo, era básicamente un 8086 de 16 bits (que tenía un bus externo completo de 16 bits) pero con solo la mitad de las líneas de bus para ahorrar costos de producción. Debido a esto, el 8088 necesitaba dos veces los ciclos de reloj para acceder a la memoria que el 8086, ya que tenía que hacer dos lecturas para obtener la palabra completa de 16 bits. La parte interesante, el 8086 puede hacer una lectura alineada de 16 bits en un solo ciclo, las lecturas no alineadas toman 2. El hecho de que el 8088 tenía un bus de media palabra enmascara esta desaceleración.
joshperry
2
@joshperry: Corrección leve: el 8086 puede hacer una lectura de 16 bits alineada con palabras en cuatro ciclos, mientras que las lecturas no alineadas toman ocho . Debido a la interfaz de memoria lenta, el tiempo de ejecución en máquinas basadas en 8088 generalmente está dominado por las recuperaciones de instrucciones. Una instrucción como "MOV AX, BX" es nominalmente un ciclo más rápido que "XCHG AX, BX", pero a menos que esté precedida o seguida de una instrucción cuya ejecución tome más de cuatro ciclos por byte de código, tomará cuatro ciclos más para ejecutar. En el 8086, ir a buscar el código a veces puede seguir el ritmo de ejecución, sino en los 8088 menos que use ...
supercat
1
Muy cierto, @martin. Eliminé esos bytes de relleno para enfocar la discusión dentro de la estructura, pero tal vez sería mejor incluirlos.
joshperry
22

puedes hacerlo con algunos procesadores ( el nehalem puede hacer esto ), 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, tenías que buscar 64 bits a la vez , y fue significativamente más fácil obtenerlos en 'fragmentos' alineados de 64 bits.

Por lo tanto, si desea obtener un solo byte, obtuvo el fragmento de 64 bits y luego ocultó los bits que no deseaba. 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 cambiar los datos al lugar correcto. Peor aún, si deseaba una variable de 2 bytes, pero se dividía en 2 fragmentos, entonces eso requería el doble de accesos de memoria requeridos.

Entonces, como todos piensan que la memoria es barata, simplemente hicieron que el compilador alinee los datos en los tamaños de los fragmentos del procesador para que su código se ejecute más rápido y más eficientemente a costa de la memoria desperdiciada.

gbjbaanb
fuente
5

Básicamente, 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.

Entonces, la CPU lee en el caché L1 en el chip, que a menudo es de 32 KB en estos días. Pero el bus de memoria que conecta el 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 .

Entonces:

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

Los accesos desalineados ocasionalmente se superpondrán con dos líneas de caché, y esto requerirá una lectura de caché completamente nueva para obtener los datos. Incluso podría pasar por alto la DRAM.

Además, una parte de la CPU tendrá que pararse sobre su cabeza para juntar un solo objeto 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 la otra, los bits de orden muy bajo.

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

En cualquier caso, la segunda lectura de memoria que a veces es necesaria ralentizaría la tubería, sin importar cuánto hardware dedicado (hipotética e insensatamente) se dedicara a reparar las operaciones de memoria desalineadas.

DigitalRoss
fuente
5

@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 las diferentes alineaciones de palabras. Además, aquí hay un enlace a una esencia de Github con el código para la prueba. El código de prueba está adaptado del artículo escrito por Jonathan Rentzsch al que @joshperry hizo referencia. Las pruebas se realizaron en un Macbook Pro con un procesador Intel Core i7 de cuatro núcleos a 2,8 GHz de 64 bits y 16 GB de RAM.

ingrese la descripción de la imagen aquí

adino
fuente
44
¿Qué significan xy ycoordinan?
shuva
1
¿Qué generación core i7? (¡Gracias por publicar enlaces al código!)
Nick Desaulniers
2

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 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 no alineada de 32 bits requeriría que algunos sistemas de memoria devuelvan datos de una dirección, y que algunos devuelvan datos de la siguiente dirección más alta. Aunque hay algunos sistemas de memoria que están optimizados para poder cumplir con tales solicitudes (además de su dirección, efectivamente tienen una señal de "más uno" que les hace usar una dirección más alta que la especificada), tal característica agrega un costo considerable y complejidad para un sistema de memoria;

Super gato
fuente
2

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 , por lo que solo se puede acceder a las direcciones alineadas de 32 bits en un solo ciclo de bus.

Entonces, si una palabra abarca un límite de alineación de dirección, 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 acceso no alineado y generarán una excepción en tales intentos, por lo que el código de acceso no alineado generado por el compilador requiere no solo ciclos de bus adicionales, sino instrucciones adicionales, lo que lo hace aún menos eficiente.

Clifford
fuente
0

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

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

Una carga de 32 bits frente a cuatro cargas de 8 bits no hará 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.

James Anderson
fuente
En Sparc, esto fue un "Error de autobús", de ahí el capítulo "Error de autobús, tome el tren" en "Programación experta en C de Peter Van der Linden: Secretos profundos en C"
jjg