Quanto è importante l'allineamento della memoria? Importa ancora?


15

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

Ma anche con questo ho ancora alcune domande al riguardo:

  1. Al di fuori del sistema incorporato, nel nostro computer abbiamo spesso enormi blocchi di memoria 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 la sua memoria riorganizzata 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 a riguardo?), In quel caso, la differenza è davvero significativa? Ci sono più vantaggi di questi due?
  3. Nel link dell'articolo, al capitolo 5, l'autore dice:

    Attenzione: in C ++, le classi che sembrano strutture possono infrangere questa regola! (Se lo fanno o no dipende da come vengono implementate le classi di base e le funzioni dei membri virtuali e varia in base al compilatore.)

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

    Hai idea di come funziona l'allineamento della memoria esattamente in C ++ poiché sembra avere alcune differenze?

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


I compilatori C ++ sono più propensi a fare questo (inserire imbottitura dove è necessario o utile) per te. Dal link che hai citato, cerca nella sezione 12 "Strumenti" le cose che puoi usare.
rwong,

Risposte:


11

Sì, sia l'allineamento che la disposizione dei dati possono fare una grande differenza in termini di prestazioni, non solo del pochi percento ma da poche a molte centinaia di percento.

Prendi questo loop, due istruzioni contano se esegui abbastanza loop.

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

Con e senza cache, e con l'allineamento con e senza lancio della cache nella previsione del ramo e puoi variare le prestazioni di queste due istruzioni di un importo significativo (timer tick):

min      max      difference
00016DDE 003E025D 003C947F

Un test delle prestazioni che puoi fare facilmente da solo. aggiungere o rimuovere nops attorno al codice in prova ed eseguire un accurato processo di temporizzazione, spostare le istruzioni in prova lungo un intervallo sufficientemente ampio di indirizzi da toccare i bordi delle linee della cache, ecc.

Lo stesso tipo di cose con gli accessi ai dati. Alcune architetture lamentano accessi non allineati (ad esempio eseguendo una lettura a 32 bit all'indirizzo 0x1001), fornendo un errore di dati. Alcuni di quelli che è possibile disabilitare l'errore e subiscono il colpo di prestazione. Altri che consentono accessi non allineati ti danno solo il successo delle prestazioni.

A volte sono "istruzioni" ma il più delle volte sono cicli di clock / bus.

Guarda le implementazioni memcpy in gcc per vari target. Supponiamo che tu stia copiando una struttura che è 0x43 byte, potresti trovare un'implementazione che copia un byte lasciando 0x42 quindi copia 0x40 byte in grandi blocchi efficienti quindi l'ultimo 0x2 può fare come due byte singoli o come trasferimento a 16 bit. Allineamento e destinazione entrano in gioco se gli indirizzi di origine e destinazione sono sullo stesso allineamento, diciamo 0x1003 e 0x2003, quindi potresti fare un byte, quindi 0x40 in grossi pezzi quindi 0x2, ma se uno è 0x1002 e l'altro 0x1003, allora si ottiene veramente brutto e molto lento.

Il più delle volte sono cicli di bus. O peggio il numero di trasferimenti. Prendi un processore con un bus dati a 64 bit, come ARM, ed esegui un trasferimento di quattro parole (lettura o scrittura, LDM o STM) all'indirizzo 0x1004, che è un indirizzo allineato a parola, e perfettamente legale, ma se il bus è 64 bit di larghezza è probabile che la singola istruzione si trasformi in tre trasferimenti in questo caso un 32 bit a 0x1004, un 64 bit a 0x1008 e un 32 bit a 0x100A. Ma se avessi le stesse istruzioni ma all'indirizzo 0x1008, potresti fare un unico trasferimento di quattro parole all'indirizzo 0x1008. Ad ogni trasferimento è associato un tempo di installazione. Quindi la differenza di indirizzo da 0x1004 a 0x1008 da sola può essere parecchie volte più veloce, anche / esp quando si utilizza una cache e tutti sono hit della cache.

Parlando di, anche se si fa una lettura di due parole all'indirizzo 0x1000 vs 0x0FFC, lo 0x0FFC con errori cache causerà due letture della riga cache dove 0x1000 è una riga cache, si ha comunque la penalità di una riga cache per un caso casuale accesso (lettura di più dati rispetto all'utilizzo) ma poi raddoppia. Come sono allineate le strutture o i dati in generale e la frequenza di accesso a tali dati, ecc., Può causare il blocco della cache.

Puoi finire con lo striping dei tuoi dati in modo tale che mentre elabori i dati puoi creare sfratti, potresti essere sfortunato e finire usando solo una frazione della tua cache e mentre salti attraverso di essa il successivo BLOB di dati si scontra con un BLOB precedente . Mescolando i dati o riorganizzando le funzioni nel codice sorgente, ecc., È possibile creare o rimuovere le collisioni, poiché non tutte le cache sono create uguali, il compilatore non ti aiuterà qui, è su di te. Anche il rilevamento del miglioramento delle prestazioni o del miglioramento dipende da te.

Tutto ciò che abbiamo aggiunto per migliorare le prestazioni, bus di dati più ampi, pipeline, cache, previsione di diramazioni, unità / percorsi di esecuzione multipli, ecc. Molto spesso aiuterà, ma hanno tutti punti deboli, che possono essere sfruttati intenzionalmente o accidentalmente. Il compilatore o le librerie possono fare molto poco, se sei interessato alle prestazioni devi mettere a punto e uno dei maggiori fattori di ottimizzazione è l'allineamento del codice e dei dati, non solo allineato su 32, 64, 128, 256 limiti di bit, ma anche dove le cose sono relative l'una all'altra, si desidera che i loop pesantemente usati o i dati riutilizzati non arrivino allo stesso modo cache, ognuno di loro vuole il proprio. I compilatori possono aiutare, ad esempio, a ordinare le istruzioni per un'architettura super scalare, riordinando le istruzioni l'una rispetto all'altra, non importa,

La più grande svista è l'ipotesi che il processore sia il collo di bottiglia. Non è vero per un decennio o più, l'alimentazione del processore è il problema ed è qui che entrano in gioco problemi come hit delle prestazioni di allineamento, blocco della cache, ecc. Con un po 'di lavoro anche a livello di codice sorgente, la riorganizzazione dei dati in una struttura, l'ordinamento delle dichiarazioni variabili / struttura, l'ordinamento delle funzioni all'interno del codice sorgente e un po' di codice aggiuntivo per allineare i dati, possono migliorare le prestazioni più volte o Di Più.


+1 se solo per il tuo ultimo paragrafo. La larghezza di banda della memoria è il problema più critico per chiunque tenti di scrivere codice veloce oggi, non il conteggio delle istruzioni. Ciò significa che l'ottimizzazione delle cose per ridurre i mancati cache, che può essere fatto modificando l'allineamento in molte circostanze, è estremamente importante.
Jules il

Se il tuo codice e i tuoi dati vengono memorizzati nella cache e esegui un numero sufficiente di cicli / cicli su tali dati, le istruzioni contano e dove si trovano le istruzioni all'interno di una linea di recupero, dove i rami atterrano all'interno del tubo rispetto a ciò su cui si basano, contano. Ma nei sistemi basati su dram e / o flash devi prima preoccuparti di alimentare il processore sì.
old_timer

15

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

Alcuni processori in realtà non possono eseguire letture su indirizzi non allineati. Se si esegue su tale hardware e si memorizzano i numeri interi non allineati, è probabile che sia necessario leggerli con due istruzioni seguite da alcune ulteriori istruzioni per ottenere i vari byte nei posti giusti in modo da poterli effettivamente utilizzare . Quindi i dati allineati sono fondamentali per le prestazioni.

La buona notizia è che per lo più 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. Devi solo iniziare a pensarci se stai prendendo il controllo diretto della rappresentazione in memoria dei tuoi dati, che non è necessario da nessuna parte nelle vicinanze come una volta. È una cosa interessante da sapere e assolutamente fondamentale sapere se vuoi capire l'uso della memoria da varie strutture che stai creando e come forse riorganizzare le cose per essere più efficienti (evitando il riempimento). Ma a meno che tu non abbia bisogno di quel tipo di controllo (e per la maggior parte dei sistemi non lo fai), puoi tranquillamente passare un'intera carriera senza saperlo o preoccupartene.


1
In particolare, ARM non supporta l'accesso non allineato. E questa è la CPU quasi tutto ciò che utilizza il cellulare.
Jan Hudec,

Si noti inoltre che Linux emula l'accesso non allineato a un costo di runtime, ma Windows (CE e telefono) non lo fa e il tentativo di accesso non allineato causerà semplicemente il crash dell'applicazione.
Jan Hudec,

2
Anche se questo è per lo più vero, nota che alcune piattaforme (incluso x86) hanno requisiti di allineamento diversi a seconda delle istruzioni che verranno utilizzate , il che non è facile per il compilatore, quindi a volte devi pad per assicurarti alcune operazioni (ad esempio le istruzioni SSE, molte delle quali richiedono un allineamento di 16 byte) possono essere utilizzate per alcune operazioni. Inoltre, l'aggiunta di un'ulteriore imbottitura in modo che due elementi utilizzati frequentemente insieme si trovino sulla stessa riga della cache (anche 16 byte) può avere un effetto enorme sulle prestazioni in alcuni casi e non è automatizzato.
Jules il

3

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

Elencherò solo alcuni esempi:

  1. Da questa risposta :

Normalmente, il microcodice recupererà la quantità corretta di 4 byte dalla memoria, ma se non è allineato, dovrà recuperare due posizioni di 4 byte dalla memoria e ricostruire la quantità desiderata di 4 byte dai byte appropriati delle due posizioni

  1. Il set di istruzioni SSE richiede un allineamento speciale. In caso contrario, è necessario utilizzare funzioni speciali per caricare e archiviare i dati nella memoria non allineata. Ciò significa due istruzioni extra.

Se non stai lavorando su algoritmi critici per le prestazioni, dimentica gli allineamenti di memoria. Non è realmente necessario per la normale programmazione.


1

Tendiamo ad evitare situazioni in cui conta. Se importa, importa. I dati non allineati accadevano ad esempio durante l'elaborazione di dati binari, che al giorno d'oggi sembra essere evitato (le persone usano molto XML o JSON).

Se in qualche modo riesci a creare un array di numeri interi non allineato, quindi su un tipico processore Intel il tuo codice che elabora tale array verrà eseguito un po 'più lentamente 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 molto, molto più lentamente o dare risultati errati, a seconda del modello del processore e del sistema operativo, se si utilizzano dati non allineati senza dirlo al compilatore.

Spiegare il riferimento a C ++: in C, tutti i campi in una struttura devono essere memorizzati in ordine di memoria crescente. Quindi se hai campi char / double / char e vuoi avere tutto allineato, avrai un byte char, sette byte inutilizzato, otto byte double, un byte char, sette byte inutilizzato. Nelle strutture C ++ è lo stesso per compatibilità. Ma per le strutture, il compilatore potrebbe riordinare i campi, quindi potresti avere un byte char, un altro byte char, sei byte inutilizzato, doppio 8 byte. Utilizzo di 16 anziché 24 byte. Nelle strutture C, gli sviluppatori di solito eviterebbero quella situazione e in primo luogo avrebbero i campi in un ordine diverso.


1
I dati non allineati si verificano in memoria. I programmi che non dispongono di strutture dati adeguatamente imballate possono subire enormi penali di prestazione anche per un ordinamento dei valori apparentemente insignificante. Nel codice lthreaded, ad esempio, due valori in una singola riga della cache causeranno enormi blocchi della pipeline quando due thread accedono ad essi contemporaneamente (ignorando i problemi di sicurezza dei thread, ovviamente).
Greyfade,

Un compilatore C ++ può riordinare i campi solo a determinate condizioni, che probabilmente non sono soddisfatte se non si è a conoscenza di tali regole. Inoltre, non sono a conoscenza di alcun compilatore C ++ che utilizza effettivamente questa libertà.
Sjoerd,

1
Non ho mai visto un compilatore C riordinare i campi. Per esempio, ho visto molte imbottiture di inserti e allineamenti tra caratteri /
ints


1

Quanto è importante l'allineamento della memoria? Importa ancora?

Sì. No. Dipende.

Al di fuori del sistema incorporato, nel nostro computer abbiamo spesso enormi blocchi di memoria 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 la sua memoria riorganizzata e allineata?

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

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 a riguardo?), In quel caso, la differenza è davvero significativa? Ci sono più vantaggi di questi due?

Può essere. È qualcosa da (eventualmente) tenere a mente durante la scrittura del codice, ma nella maggior parte dei casi non dovrebbe importare (ovvero, dispongo ancora le variabili del mio membro in base all'impronta della memoria e alla frequenza di accesso - che dovrebbe facilitare la memorizzazione nella cache - ma lo faccio per facilità d'uso / lettura e refactoring del codice, non per scopi di memorizzazione nella cache).

Hai idea di come funziona l'allineamento della memoria esattamente in C ++ poiché sembra avere alcune differenze?

L'ho letto quando è uscito il materiale di alignof (C ++ 11?) Da allora non mi sono preoccupato (sto facendo principalmente applicazioni desktop e sviluppo di server back-end in questi giorni).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.