Sì, sia l'allineamento che la disposizione dei dati possono fare una grande differenza in termini di prestazioni, non solo da pochi punti percentuali, ma da pochi a molte centinaia di punti percentuali.
Segui questo ciclo, due istruzioni contano se si eseguono cicli sufficienti.
.globl ASMDELAY
ASMDELAY:
subs r0,r0,#1
bne ASMDELAY
bx lr
Con e senza cache e con allineamento con e senza lancio della cache nella previsione delle filiali e puoi variare le prestazioni di queste due istruzioni di una quantità significativa (tick del timer):
min max difference
00016DDE 003E025D 003C947F
Un test delle prestazioni che puoi fare facilmente da solo. aggiungi o rimuovi i nops attorno al codice sotto test e fai un accurato lavoro di tempistica, sposta le istruzioni sotto test lungo un intervallo sufficientemente ampio di indirizzi per toccare i bordi delle linee della cache, ecc.
Lo stesso tipo di cose con accesso ai dati. Alcune architetture si lamentano degli accessi non allineati (ad esempio, eseguendo una lettura a 32 bit all'indirizzo 0x1001), dando un errore ai dati. Alcuni di quelli che è possibile disabilitare l'errore e prendere il colpo di prestazioni. Altri che consentono accessi non allineati ti danno appena il risultato in termini di prestazioni.
A volte sono "istruzioni" ma il più delle volte si tratta di cicli orologio / bus.
Guarda le implementazioni di memcpy in gcc per vari obiettivi. Diciamo che stai copiando una struttura che è 0x43 byte, potresti trovare un'implementazione che copia un byte che lascia 0x42 quindi copia 0x40 byte in grandi blocchi efficienti quindi l'ultimo 0x2 può fare come due singoli byte o come un trasferimento a 16 bit. L'allineamento e il bersaglio entrano in gioco se gli indirizzi di origine e destinazione sono sullo stesso allineamento, dicono 0x1003 e 0x2003, quindi si può fare l'un byte, quindi 0x40 in grandi blocchi quindi 0x2, ma se uno è 0x1002 e l'altro 0x1003, allora diventa vero brutto e vero lento.
Il più delle volte sono i cicli del bus. O peggio il numero di trasferimenti. Prendi un processore con un bus dati ampio a 64 bit, come ARM, e fai un trasferimento di quattro parole (leggi o scrivi, LDM o STM) all'indirizzo 0x1004, che è un indirizzo allineato alla parola e perfettamente legale, ma se il bus è 64 A livello di bit è probabile che la singola istruzione si trasformi in tre trasferimenti in questo caso a 32 bit a 0x1004, a 64 bit a 0x1008 e a 32 bit a 0x100A. Ma se tu avessi la stessa istruzione ma all'indirizzo 0x1008 potrebbe fare un singolo trasferimento di quattro parole all'indirizzo 0x1008. A ogni trasferimento è associato un tempo di configurazione. Quindi la differenza tra 0x1004 e 0x1008 di indirizzo può essere parecchie volte più veloce, anche / esp quando si usa una cache e tutti sono hit della cache.
A proposito, anche se si fa una parola di due parole all'indirizzo 0x1000 vs 0x0FFC, il 0x0FFC con errori di cache causerà due letture della riga di cache dove 0x1000 è una linea di cache, si ha comunque la penalità di una riga della cache letta per un accesso casuale (leggendo più dati che usare) ma poi raddoppia. Il modo in cui le tue strutture sono allineate oi tuoi dati in generale e la tua frequenza di accesso a tali dati, ecc., Può causare il thrashing della cache.
Puoi finire con lo striping dei dati in modo tale che mentre elabori i dati puoi creare sfratti, potresti diventare veramente sfortunato e finire con l'usare solo una piccola parte della tua cache e mentre lo fai saltare il prossimo blob di dati si scontra con un blob precedente. Mescolando i tuoi dati o riordinando le funzioni nel codice sorgente, ecc. Puoi creare o rimuovere collisioni, poiché non tutte le cache sono create uguali al compilatore, non ti aiuterà in questo caso. Anche rilevare il successo o il miglioramento delle prestazioni è su di te.
Tutte le cose che abbiamo aggiunto per migliorare le prestazioni, bus dati più ampi, pipeline, cache, previsione delle filiali, unità / percorsi multipli di esecuzione, ecc. Molto spesso aiutano, ma hanno tutti punti deboli, che possono essere sfruttati intenzionalmente o accidentalmente. C'è poco che il compilatore o le librerie possano fare al riguardo, se sei interessato alle prestazioni devi sintonizzarti e uno dei maggiori fattori di sintonizzazione è l'allineamento del codice e dei dati, non solo allineati su 32, 64, 128, 256 i confini di bit, ma anche dove le cose sono relative l'una all'altra, si vogliono loop pesantemente utilizzati o dati riutilizzati per non atterrare nello stesso modo in cui sono memorizzati, ognuno vuole il proprio. I compilatori possono aiutare ad esempio ad ordinare le istruzioni per un'architettura super scalare, riordinando le istruzioni che non hanno importanza l'una relativamente all'altra, possono ottenere un grande guadagno o colpo se non si utilizzano efficientemente i percorsi di esecuzione, ma bisogna dire al compilatore su cosa stai lavorando.
La più grande supervisione è l'assunzione che il processore sia il collo di bottiglia. Non è vero da un decennio o più, l'alimentazione del processore è il problema ed è qui che entrano in gioco problemi come i colpi delle prestazioni di allineamento, il "cache thrashing", ecc. Con un po 'di lavoro anche a livello di codice sorgente, riorganizzare i dati in una struttura, ordinare dichiarazioni variabili / struct, ordinare le funzioni all'interno del codice sorgente e un piccolo extra per allineare i dati, può migliorare le prestazioni più volte o di più.