Quanto è importante l'allineamento della memoria? Ha ancora importanza?

12

Da qualche tempo, ho cercato e letto molto sull'allineamento della memoria, su come funziona e su come usarlo. L'articolo più pertinente che ho trovato per ora è questo .

Ma anche con questo ho ancora alcune domande a riguardo:

  1. Al di fuori del sistema embedded, abbiamo spesso enormi quantità di memoria nel nostro computer che rendono la gestione della memoria molto meno critica, sono completamente ottimizzato, ma ora è davvero qualcosa che può fare la differenza se confrontiamo lo stesso programma con o senza memoria riarrangiata e allineata?
  2. L'allineamento della memoria ha altri vantaggi? Ho letto da qualche parte che la CPU funziona meglio / più velocemente con memoria allineata perché richiede meno istruzioni per l'elaborazione (se uno di voi ha un link per un articolo / benchmark su di esso?), In tal caso, la differenza è davvero significativa? C'è più vantaggi di questi due?
  3. Nel link all'articolo, al capitolo 5, l'autore dice:

    Beware: in C++, classes that look like structs may break this rule! (Whether they do or not depends on how base classes and virtual member functions are implemented, and varies by compiler.)

  4. L'articolo parla principalmente di strutture, ma la dichiarazione delle variabili locali è influenzata anche da questa necessità?

    Hai idea di come l'allineamento della memoria funzioni esattamente in C ++ dal momento che sembra avere alcune differenze?

Questa precedente domanda contiene la parola "allineamento" , ma non fornisce alcuna risposta alle domande precedenti.

    
posta Kane 19.08.2016 - 10:16
fonte

6 risposte

9

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ù.

    
risposta data 20.08.2016 - 22:04
fonte
15

Sì, l'allineamento della memoria è ancora importante.

In realtà alcuni processori non possono eseguire letture su indirizzi non allineati. Se stai usando questo tipo di hardware, e memorizzi i tuoi interi non allineati, probabilmente dovrai leggerli con due istruzioni seguite da altre istruzioni per portare i vari byte nei posti giusti in modo che tu possa effettivamente usarli . I dati allineati sono quindi cruciali per le prestazioni.

La buona notizia è che per la maggior parte in realtà non devi preoccupartene. Quasi tutti i compilatori per quasi tutte le lingue produrranno codice macchina che rispetta i requisiti di allineamento del sistema di destinazione. Hai solo bisogno di iniziare a pensarci se stai prendendo il controllo diretto della rappresentazione in-memory dei tuoi dati, che non è necessaria da nessuna parte vicino come una volta. È una cosa interessante da conoscere e assolutamente fondamentale da sapere se vuoi capire l'utilizzo della memoria da varie strutture che stai creando e come riorganizzare le cose per renderle più efficienti (evitando il padding). Ma a meno che tu non abbia bisogno di quel tipo di controllo (e per la maggior parte dei sistemi semplicemente non lo fai), puoi vivere felicemente un'intera carriera senza saperlo o prendertene cura.

    
risposta data 19.08.2016 - 10:35
fonte
3

Sì, è ancora importante, e in alcuni algoritmi critici per le prestazioni, non puoi fare affidamento sul compilatore.

Ho intenzione di elencare solo alcuni esempi:

  1. Da questa risposta :

Normally, the microcode will fetch the proper 4-byte quantity from memory, but if it's not aligned, it will have to fetch two 4-byte locations from memory and reconstruct the desired 4-byte quantity from the appropriate bytes of the two locations

  1. Il set di istruzioni SSE richiede un allineamento speciale. Se non è soddisfatto, è necessario utilizzare funzioni speciali per caricare e memorizzare i dati in memoria non allineata. Ciò significa due istruzioni extra.

Se non stai lavorando su un algoritmo di performance critica, dimentica gli allineamenti di memoria. Non è veramente necessario per la normale programmazione.

    
risposta data 19.08.2016 - 18:31
fonte
1

Tendiamo ad evitare situazioni in cui è importante. Se è importante, conta. Dati non allineati utilizzati per esempio durante l'elaborazione di dati binari, che sembra essere evitato al giorno d'oggi (la gente usa molto XML o JSON).

Se in qualche modo riesci a creare un array non allineato di numeri interi, in un tipico processore Intel il tuo codice elaborerà quell'array più lento rispetto ai dati allineati. Su un processore ARM funziona un po 'più lentamente se si dice al compilatore che i dati non sono allineati. Può funzionare in modo orribile, molto più lento o dare risultati errati, a seconda del modello del processore e del sistema operativo, se si usano dati non allineati senza dire al compilatore.

Spiegazione del riferimento a C ++: In C, tutti i campi di una struttura devono essere memorizzati in ordine crescente di memoria. Quindi se hai campi char / double / char e vuoi avere tutto allineato, avrai un byte char, sette byte inutilizzati, otto byte double, un byte char, sette byte inutilizzati. Nelle strutture C ++ è lo stesso per la compatibilità. Ma per le struct, il compilatore può riordinare i campi, quindi potresti avere un byte char, un altro byte char, sei byte non usati, 8 byte double. Utilizzo di 16 anziché di 24 byte. In C structs, gli sviluppatori di solito evitavano quella situazione e in primo luogo hanno i campi in un ordine diverso.

    
risposta data 19.08.2016 - 16:50
fonte
1

Molti punti positivi sono già menzionati nelle risposte precedenti. Solo per aggiungere anche nei sistemi non integrati che si occupano di ricerca / estrazione dei dati, le prestazioni delle memorie e i tempi di accesso sono così importanti che oltre al codice di assemblaggio dell'allineamento viene scritto lo stesso.

Raccomando anche una lettura interessante: link

    
risposta data 21.08.2016 - 07:05
fonte
1

How important is memory alignment? Does it still matter?

Sì. No. Dipende.

Out of embedded system, we often have huge chunk of memory in our computer that make memory management a lot less critic, I am completly into optimization, but now, is it really something that can make the difference if we compare the same program with or without it's memory rearranged and aligned?

La tua applicazione avrà un minore ingombro di memoria e funzionerà più velocemente se è allineata correttamente. Nella tipica applicazione desktop, non importa fuori da casi rari / atipici (come la tua applicazione che termina sempre con lo stesso collo di bottiglia delle prestazioni e richiede ottimizzazioni). Cioè, l'app sarà più piccola e veloce se correttamente allineata, ma per la maggior parte dei casi pratici non dovrebbe influenzare l'utente in un modo o nell'altro.

Is memory alignment have other advantages? I read somewhere that CPU work better/faster with aligned memory because that take it less instructions to process (if one of you have a link for an article/benchmark about it?), in that case, is the difference really significant? Is there more advantages than these two?

Può essere. È qualcosa da tenere presente (possibilmente) durante la scrittura del codice, ma nella maggior parte dei casi non dovrebbe importare (cioè, sistemo ancora le variabili membro in base all'impronta di memoria e alla frequenza di accesso - che dovrebbe facilitare il caching - ma lo faccio per facilità d'uso / lettura e refactoring del codice, non per scopi di cache).

Have you any idea of how memory alignment work exactly in C++ since it seem to have some differences?

Ne ho letto quando è uscito il file alignof (C ++ 11?). Non mi sono preoccupato di questo dato che (sto facendo principalmente applicazioni desktop e sviluppo di server backend in questi giorni).

    
risposta data 24.08.2016 - 12:47
fonte

Leggi altre domande sui tag