Desde hace algún tiempo, he buscado y leído mucho sobre la alineación de la memoria, cómo funciona y cómo usarla. El artículo más relevante que he encontrado por ahora es este .
Pero incluso con eso todavía tengo algunas preguntas al respecto:
- Fuera del sistema embebido, a menudo tenemos una gran cantidad de memoria en nuestra computadora que hace que la administración de la memoria sea mucho menos crítica, estoy completamente en la optimización, pero ahora, es realmente algo que puede marcar la diferencia si comparamos el mismo programa con o sin memoria reorganizada y alineada?
- ¿La alineación de la memoria tiene otras ventajas? Leí en alguna parte que la CPU funciona mejor / más rápido con la memoria alineada porque eso requiere menos instrucciones para procesar (si uno de ustedes tiene un enlace para un artículo / punto de referencia al respecto), en ese caso, ¿la diferencia es realmente significativa? ¿Hay más ventajas que estos dos?
- En el enlace del artículo, en el capítulo 5, el autor dice:
Cuidado: en C ++, ¡las clases que parecen estructuras pueden romper esta regla! (Si lo hacen o no depende de cómo se implementen las clases base y las funciones virtuales de los miembros, y varía según el compilador).
El artículo habla principalmente de estructuras, pero ¿la declaración de variables locales también se ve afectada por esta necesidad?
¿Tienes alguna idea de cómo funciona la alineación de la memoria exactamente en C ++ ya que parece tener algunas diferencias?
Esta pregunta anterior contiene la palabra "alineación", pero no proporciona ninguna respuesta a las preguntas anteriores.
fuente
Respuestas:
Sí, tanto la alineación como la disposición de sus datos pueden marcar una gran diferencia en el rendimiento, no solo de un pequeño porcentaje, sino de varios cientos a un porcentaje.
Tome este bucle, dos instrucciones importan si ejecuta suficientes bucles.
Con y sin caché, y con alineación con y sin caché, arroje la predicción de rama y puede variar el rendimiento de esas dos instrucciones en una cantidad significativa (tics de temporizador):
Una prueba de rendimiento que puedes hacer tú mismo muy fácilmente. agregue o elimine nops alrededor del código bajo prueba y haga un trabajo preciso de sincronización, mueva las instrucciones bajo prueba a lo largo de un rango lo suficientemente amplio de direcciones para tocar los bordes de las líneas de caché, etc.
El mismo tipo de cosas con los accesos a datos. Algunas arquitecturas se quejan de accesos no alineados (por ejemplo, realizando una lectura de 32 bits en la dirección 0x1001), dándole una falla de datos. A algunos de ellos se les puede desactivar la falla y recibir el golpe de rendimiento. Otros que permiten accesos no alineados solo obtienen el impacto en el rendimiento.
A veces son "instrucciones", pero la mayoría de las veces son ciclos de reloj / bus.
Mire las implementaciones de memcpy en gcc para varios objetivos. Supongamos que está copiando una estructura que tiene 0x43 bytes, puede encontrar una implementación que copia un byte dejando 0x42, luego copia 0x40 bytes en grandes bloques eficientes y luego el último 0x2 puede hacerlo como dos bytes individuales o como una transferencia de 16 bits. La alineación y el objetivo entran en juego si las direcciones de origen y de destino están en la misma alineación, digamos 0x1003 y 0x2003, entonces podría hacer un byte, luego 0x40 en fragmentos grandes, luego 0x2, pero si uno es 0x1002 y el otro 0x1003, entonces se obtiene muy feo y muy lento.
La mayoría de las veces son ciclos de autobuses. O peor aún, el número de transferencias. Tome un procesador con un bus de datos de 64 bits de ancho, como ARM, y realice una transferencia de cuatro palabras (lectura o escritura, LDM o STM) en la dirección 0x1004, es una dirección alineada con palabras, y perfectamente legal, pero si el bus es 64 bits de ancho es probable que la instrucción individual se convierta en tres transferencias en este caso, 32 bits a 0x1004, 64 bits a 0x1008 y 32 bits a 0x100A. Pero si tuviera la misma instrucción pero en la dirección 0x1008, podría hacer una sola transferencia de cuatro palabras en la dirección 0x1008. Cada transferencia tiene un tiempo de configuración asociado. Por lo tanto, la diferencia de direcciones de 0x1004 a 0x1008 por sí misma puede ser varias veces más rápida, incluso / esp cuando se usa un caché y todos son aciertos de caché.
Hablando de eso, incluso si lee dos palabras en la dirección 0x1000 frente a 0x0FFC, el 0x0FFC con errores de caché causará dos lecturas de línea de caché donde 0x1000 es una línea de caché, tiene la penalidad de una línea de caché leída de todos modos para un azar acceso (leer más datos que usar) pero luego eso se duplica. La forma en que se alinean sus estructuras o sus datos en general y su frecuencia de acceso a esos datos, etc., pueden causar la pérdida de memoria caché.
Puede terminar eliminando sus datos de manera tal que a medida que procesa los datos puede crear desalojos, podría ser realmente desafortunado y terminar usando solo una fracción de su caché y, a medida que salta, el siguiente bloque de datos colisiona con un blob anterior . Al mezclar sus datos o reorganizar las funciones en el código fuente, etc., puede crear o eliminar colisiones, ya que no todas las memorias caché se crean de la misma manera, el compilador no lo ayudará aquí. Incluso detectar el impacto o la mejora del rendimiento depende de usted.
Todas las cosas que hemos agregado para mejorar el rendimiento, buses de datos más amplios, tuberías, cachés, predicción de ramales, múltiples unidades / rutas de ejecución, etc. A menudo ayudarán, pero todos tienen puntos débiles, que pueden explotarse intencionalmente o accidentalmente. Es muy poco lo que el compilador o las bibliotecas pueden hacer al respecto, si está interesado en el rendimiento necesita ajustar y uno de los factores de ajuste más importantes es la alineación del código y los datos, no solo alineados en 32, 64, 128, 256 límites de bits, pero también donde las cosas son relativas entre sí, desea bucles muy utilizados o datos reutilizados para no aterrizar en la misma forma de caché, cada uno quiere el suyo. Los compiladores pueden ayudar, por ejemplo, al ordenar instrucciones para una arquitectura súper escalar, reorganizando instrucciones que no importan entre sí,
El mayor descuido es la suposición de que el procesador es el cuello de botella. No ha sido así durante una década o más, alimentar el procesador es el problema y es allí donde entran en juego problemas como el rendimiento de la alineación, el almacenamiento en caché, etc. Con un poco de trabajo incluso en el nivel del código fuente, reorganizar los datos en una estructura, ordenar las declaraciones de variables / estructuras, ordenar las funciones dentro del código fuente y un poco de código adicional para alinear los datos, puede mejorar el rendimiento varias veces o más.
fuente
Sí, la alineación de la memoria todavía importa.
Algunos procesadores en realidad no pueden realizar lecturas en direcciones no alineadas. Si está ejecutando en dicho hardware y almacena sus enteros no alineados, es probable que tenga que leerlos con dos instrucciones seguidas de algunas instrucciones más para obtener los diversos bytes en los lugares correctos para que pueda usarlo . Por lo tanto, los datos alineados son críticos para el rendimiento.
La buena noticia es que en su mayoría no tiene que preocuparse. Casi cualquier compilador para casi cualquier idioma producirá código de máquina que respete los requisitos de alineación del sistema de destino. Solo necesita comenzar a pensar en ello si está tomando el control directo de la representación en memoria de sus datos, lo cual no es necesario en ningún lugar tan a menudo como antes. Es algo interesante de saber, y absolutamente crítico saber si desea comprender el uso de la memoria de varias estructuras que está creando, y cómo tal vez reorganizar las cosas para que sean más eficientes (evitando el relleno). Pero a menos que necesite ese tipo de control (y para la mayoría de los sistemas que simplemente no necesita), puede pasar felizmente una carrera completa sin saberlo ni preocuparse por él.
fuente
Sí, todavía importa, y en algunos algoritmos críticos de rendimiento, no puede confiar en el compilador.
Voy a enumerar solo algunos ejemplos:
Si no está trabajando en algoritmos críticos de rendimiento, simplemente olvídese de las alineaciones de memoria. Realmente no es necesario para la programación normal.
fuente
Tendemos a evitar situaciones en las que importa. Si importa, importa. Los datos no alineados solían ocurrir, por ejemplo, al procesar datos binarios, lo que parece evitarse hoy en día (las personas usan mucho XML o JSON).
SI de alguna manera logra crear una matriz de enteros no alineados, entonces, en un procesador Intel típico, su código procesará esa matriz un poco más lento que para los datos alineados. En un procesador ARM, funciona un poco más lento si le dice al compilador que los datos no están alineados. Puede ejecutarse mucho, mucho más lento o dar resultados incorrectos, según el modelo de procesador y el sistema operativo, si utiliza datos no alineados sin avisar al compilador.
Explicando la referencia a C ++: en C, todos los campos en una estructura deben almacenarse en orden de memoria ascendente. Entonces, si tiene campos char / double / char y desea tener todo alineado, tendrá un byte char, siete byte sin usar, ocho byte doble, un byte char, siete byte sin usar. En estructuras C ++ es lo mismo por compatibilidad. Pero para las estructuras, el compilador puede reordenar los campos, por lo que puede tener un byte char, otro byte char, seis byte sin usar, 8 byte doble. Usando 16 en lugar de 24 bytes. En estructuras C, los desarrolladores generalmente evitarían esa situación y tendrían los campos en un orden diferente en primer lugar.
fuente
Muchos puntos buenos ya se mencionan en las respuestas anteriores. Solo para agregar incluso en sistemas no integrados que se ocupan de la búsqueda / extracción de datos, el rendimiento de los asuntos de memoria y los tiempos de acceso son tan importantes que, aparte del código de ensamblaje de alineación, se escribe para el mismo.
También recomiendo una lectura que valga la pena: http://dewaele.org/~robbe/thesis/writing/references/what-every-programmer-should-know-about-memory.2007.pdf
fuente
Si. No. Depende.
Su aplicación tendrá una huella de memoria más pequeña y funcionará más rápido si está correctamente alineada. En la aplicación de escritorio típica, no importará fuera de casos raros / atípicos (como su aplicación que siempre termina con el mismo cuello de botella de rendimiento y requiere optimizaciones). Es decir, la aplicación será más pequeña y más rápida si está correctamente alineada, pero en la mayoría de los casos prácticos no debería afectar al usuario de una forma u otra.
Puede ser. Es algo a tener en cuenta (posiblemente) al escribir código, pero en la mayoría de los casos simplemente no debería importar (es decir, todavía organizo mis variables miembro por huella de memoria y frecuencia de acceso, lo que debería facilitar el almacenamiento en caché), pero lo hago para facilidad de uso / lectura y refactorización del código, no para fines de almacenamiento en caché).
Leí sobre eso cuando salió la alineación de cosas (¿C ++ 11?) No me molesté desde entonces (estoy haciendo principalmente aplicaciones de escritorio y desarrollo de servidores back-end en estos días).
fuente