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.
.globl ASMDELAY
ASMDELAY:
subs r0,r0,#1
bne ASMDELAY
bx lr
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):
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 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.