¿Qué tan importante es la alineación de la memoria? ¿Todavía importa?

12

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:

  1. Fuera del sistema integrado, 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 lo mismo ¿Programa con o sin memoria reorganizada y alineada?
  2. ¿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 procesarla (si alguno de ustedes tiene un enlace para un artículo / un punto de referencia al respecto), en ese caso, ¿la diferencia es realmente significativa? ¿Hay más ventajas que estas dos?
  3. 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. (El hecho de que lo hagan o no depende de cómo se implementan las clases base y las funciones de miembro virtual, y varía según el compilador).

  4. El artículo habla principalmente de estructuras, pero ¿la necesidad de las variables locales también se ve afectada por la declaración?

    ¿Tiene 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.

    
pregunta Kane 19.08.2016 - 10:16

6 respuestas

9

Sí, tanto la alineación como la disposición de sus datos pueden hacer una gran diferencia en el rendimiento, no solo un pequeño porcentaje, sino unos pocos a muchos cientos de un porcentaje.

Toma este bucle, dos instrucciones son importantes si ejecutas suficientes bucles.

.globl ASMDELAY
ASMDELAY:
    subs r0,r0,#1
    bne ASMDELAY
    bx lr

Con y sin caché, y con alineación con y sin tirado de caché en la predicción de bifurcación, puede variar el rendimiento de estas dos instrucciones en una cantidad significativa (temporizadores):

min      max      difference
00016DDE 003E025D 003C947F

Una prueba de rendimiento que puedes hacer tú mismo muy fácilmente. agregue o elimine nops alrededor del código bajo prueba y realice un trabajo preciso de sincronización, mueva las instrucciones bajo prueba a lo largo de un amplio rango de direcciones para tocar los bordes de las líneas de caché, etc.

Lo mismo que con los accesos de datos. Algunas arquitecturas se quejan de accesos no alineados (que realizan una lectura de 32 bits en la dirección 0x1001 por ejemplo), al darle un error de datos. Algunos de ellos pueden deshabilitar la falla y recibir el impacto de rendimiento. Los demás que permiten accesos no alineados solo obtienen el impacto de 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 destinos. Supongamos que está copiando una estructura de 0x43 bytes, puede encontrar una implementación que copie un byte dejando 0x42 y luego copie 0x40 bytes en grandes porciones eficientes; 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 destino están en la misma alineación, por ejemplo, 0x1003 y 0x2003, entonces usted podría hacer un byte, luego 0x40 en grandes segmentos y luego 0x2, pero si uno es 0x1002 y el otro 0x1003, entonces real feo y muy lento.

La mayoría de las veces son ciclos de bus. O peor, 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, que es una dirección de palabras alineadas, y es perfectamente legal, pero si el bus es 64 bits de ancho es probable que la instrucción única se convierta en tres transferencias en este caso a 32 bits a 0x1004, a 64 bits a 0x1008 y a 32 bits a 0x100A. Pero si tuviera la misma instrucción pero en la dirección 0x1008, podría realizar una única transferencia de cuatro palabras en la dirección 0x1008. Cada transferencia tiene un tiempo de configuración asociado. Por lo tanto, la diferencia de dirección 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 haces una lectura de dos palabras en la dirección 0x1000 vs 0x0FFC, la falta de memoria caché 0x0FFC causará dos lecturas de línea caché donde 0x1000 es una línea de caché, tienes la penalización de la lectura de una línea caché de todos modos para un acceso aleatorio (leer más datos que usar) pero luego se duplica. La forma en que se alinean sus estructuras o sus datos en general y la frecuencia con la que accede a esos datos, etc., puede provocar la aglomeración de caché.

Puede terminar dividiendo sus datos de manera tal que al procesar los datos, puede crear desalojos, puede tener una mala suerte y terminar usando solo una fracción de su caché y al saltar a través del siguiente blob 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 igual que el compilador no lo va a ayudar. Incluso la detección del impacto o la mejora del rendimiento está en ti.

Todas las cosas que hemos agregado para mejorar el rendimiento, buses de datos más amplios, tuberías, cachés, predicción de bifurcaciones, múltiples unidades / rutas de ejecución, etc. Ayudarán con mayor frecuencia, pero todas 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, debe afinarlo y uno de los factores de ajuste más importantes es la alineación del código y los datos, no solo de 32, 64, 128, 256 los límites de bits, pero también cuando las cosas son relativas entre sí, usted quiere que los bucles muy utilizados o los datos reutilizados no se conecten de la misma manera en la memoria caché, cada uno desea el suyo propio. Los compiladores pueden ayudar, por ejemplo, a ordenar las instrucciones para una arquitectura súper escalar, reorganizar las instrucciones que no se importan entre sí, pueden tener una gran ganancia de rendimiento o éxito si no está utilizando de manera eficiente las rutas de ejecución, pero debe informar al compilador en lo que se está ejecutando.

La mayor supervisión es la suposición de que el procesador es el cuello de botella. No ha sido así durante una década o más, el problema es alimentar al procesador y ahí es donde entran en juego problemas como los golpes de rendimiento de alineación, la memoria caché, etc. Con un poco de trabajo, incluso a nivel del código fuente, la reorganización de los datos en una estructura, el orden de las declaraciones de variables / estructura, el orden de 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.

    
respondido por el old_timer 20.08.2016 - 22:04
15

Sí, la alineación de la memoria sigue siendo importante.

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 colocar los distintos bytes en los lugares correctos para que pueda usarlos. . Por lo tanto, los datos alineados son críticos para el rendimiento.

La buena noticia es que la mayoría de las veces no tiene que preocuparse. Casi cualquier compilador para casi cualquier idioma producirá un código de máquina que respete los requisitos de alineación del sistema de destino. Solo tiene que empezar 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 ninguna parte tan a menudo como lo era antes. Es interesante saberlo, y es absolutamente esencial saber si quiere comprender el uso de la memoria de las diversas estructuras que está creando y cómo 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 simplemente no lo hace), puede pasar felizmente por toda una carrera sin saberlo o preocuparse por ello.

    
respondido por el Matthew Walton 19.08.2016 - 10:35
3

Sí, sigue siendo importante, y en algunos algoritmos críticos de rendimiento, no puedes confiar en el compilador.

Voy a enumerar solo algunos ejemplos:

  1. De esta respuesta :
  

Normalmente, el microcódigo obtendrá la cantidad correcta de 4 bytes de la memoria, pero si no está alineado, tendrá que recuperar dos ubicaciones de 4 bytes de la memoria y reconstruir la cantidad deseada de 4 bytes a partir de los bytes apropiados de la memoria. dos ubicaciones

  1. El conjunto de instrucciones SSE requiere una alineación especial. Si no se cumple, debe usar funciones especiales para cargar y almacenar datos en la memoria no alineada. Eso significa dos instrucciones adicionales.

Si no está trabajando en algoritmos críticos de rendimiento, simplemente olvídese de las alineaciones de memoria. No es realmente necesario para la programación normal.

    
respondido por el BЈовић 19.08.2016 - 18:31
1

Tendemos a evitar situaciones donde importa. Si importa, importa. Los datos no alineados solían ocurrir, por ejemplo, al procesar datos binarios, lo que parece que se evita en la actualidad (las personas usan mucho XML o JSON).

Si de alguna manera logras crear una matriz de enteros no alineados, entonces, en un procesador Intel típico, el código que procesa esa matriz se ejecutará un poco más lento que para los datos alineados. En un procesador ARM, se ejecuta un poco más lento si le dice al compilador que los datos no están alineados. Es posible que se ejecute mucho, mucho más lento o dé 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 de una estructura deben almacenarse en orden de memoria ascendente. Entonces, si tiene los campos char / double / char y desea tener todo alineado, tendrá un byte char, siete bytes sin usar, ocho bytes doble, un byte char, siete bytes sin usar. En las estructuras de C ++ es lo mismo para la compatibilidad. Pero para las estructuras, el compilador puede reordenar los campos, por lo que puede tener un byte char, otro byte char, seis bytes sin usar, 8 bytes doble. Utilizando 16 en lugar de 24 bytes. En C structs, los desarrolladores generalmente evitarían esa situación y tendrían los campos en un orden diferente en primer lugar.

    
respondido por el gnasher729 19.08.2016 - 16:50
1

Muchos puntos positivos ya se mencionaron 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 la memoria y los tiempos de acceso son tan importantes que, aparte del código de ensamblaje de alineación, se escriben para el mismo.

También recomiendo una lectura que valga la pena: enlace

    
respondido por el Varun Mishra 21.08.2016 - 07:05
1
  

¿Qué tan importante es la alineación de la memoria? ¿Todavía importa?

Sí. No. Depende.

  

Fuera del sistema integrado, 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 optimización, pero ahora, es realmente algo que puede hacer la diferencia si comparamos lo mismo programa con o sin su memoria reorganizada y alineada?

Su aplicación tendrá una huella de memoria más pequeña y funcionará más rápido si se alinea correctamente. En la aplicación de escritorio típica, no importará fuera de casos raros / atípicos (como su aplicación siempre termina con el mismo cuello de botella en el rendimiento y requiere optimizaciones). Es decir, la aplicación será más pequeña y más rápida si se alinea correctamente, pero en la mayoría de los casos prácticos no debería afectar al usuario de una forma u otra.

  

¿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 procesarla (si alguno de ustedes tiene un enlace para un artículo / un punto de referencia al respecto), en ese caso, ¿la diferencia es realmente significativa? ¿Hay más ventajas que estas dos?

Puede ser. Es algo que (posiblemente) tenga en cuenta al escribir código, pero en la mayoría de los casos simplemente no debería importar (es decir, sigo ordenando mis variables miembro según la huella de memoria y la 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 propósitos de almacenamiento en caché).

  

¿Tiene alguna idea de cómo funciona la alineación de la memoria exactamente en C ++, ya que parece tener algunas diferencias?

Leí sobre esto cuando surgió la alineación de cosas (¿C ++ 11?) No me molesté en eso porque (en la mayoría de los casos, estoy haciendo aplicaciones de escritorio y desarrollo de servidores back-end).

    
respondido por el utnapistim 24.08.2016 - 12:47

Lea otras preguntas en las etiquetas