2016-06-09 9 views
6

Ho letto da qualche parte che prima di eseguire il caricamento non allineato o memorizzare accanto al limite della pagina (ad esempio utilizzando _mm_loadu_si128/_mm_storeu_si128 intrinseco), il codice deve innanzitutto verificare se l'intero vettore (in questo caso 16 byte) appartiene alla stessa pagina e, in caso contrario, passa alle istruzioni non vettoriali. Capisco che questo è necessario per prevenire il coredump se la pagina successiva non appartiene al processo.SSE: carico non allineato e archivio che attraversa il limite della pagina

Ma che succede se entrambe le pagine appartengono al processo (ad esempio fanno parte di un buffer e conosciamo le dimensioni di quel buffer)? Ho scritto un piccolo programma di test che eseguiva il caricamento non allineato e memorizzava il limite della pagina incrociata e non si arrestava. Devo sempre controllare il limite della pagina in questo caso, o è sufficiente per assicurarmi che non esageri il buffer?

Env: Linux, x86_64, GCC

risposta

7

divisioni di pagina-Line sono un male per le prestazioni, ma non influenzano la correttezza di accessi non allineati. È sufficiente assicurarsi di non leggere oltre la fine del buffer, quando si conosce la lunghezza in anticipo.


Per correttezza, spesso è necessario preoccuparsi in sede di attuazione qualcosa come strlen, dove il ciclo si interrompe quando si trova un valore di sentinella. Quel valore potrebbe essere in qualsiasi posizione all'interno del vettore, quindi solo i carichi non allineati 16B leggeranno oltre la fine dell'array. Se il 0 di chiusura è nell'ultimo byte di una pagina e la pagina successiva non è leggibile e il puntatore di posizione corrente non è allineato, un carico che include il byte 0 includerà anche i byte dalla pagina illeggibile, quindi si verificherà un errore .

Una soluzione è eseguire scalare finché il puntatore non è allineato, quindi caricare i vettori allineati. Un carico allineato viene sempre interamente da una pagina e anche da una linea della cache. Quindi, anche se leggerete alcuni byte oltre la fine della stringa, si è certi di non commettere errori. Valgrind potrebbe essere insoddisfatto, tuttavia, ma le implementazioni della libreria standard strlen lo utilizzano.

Invece di uno scalare fino a un puntatore allineato, è possibile eseguire un vettore non allineato dall'inizio della stringa (a condizione che non attraversi una linea della pagina), quindi eseguire carichi allineati. Il primo carico allineato si sovrapporrà al primo carico non allineato, ma è assolutamente perfetto per una funzione come strlen a cui non importa se vede gli stessi dati due volte.


Potrebbe valere la pena evitando pagina-linea si divide per motivi di prestazioni. Anche se sai che il tuo puntatore src è disallineato, è spesso più veloce lasciare che l'hardware gestisca le divisioni della linea della cache. Ma prima di Skylake, le divisioni di pagina hanno una latenza extra di ~ 100 c. (Down to 5c in Skylake). Se hai più puntatori che possono essere allineati in modo diverso l'uno rispetto all'altro, non puoi sempre usare un prologo per allineare il tuo src. (Ad esempio c[i] = a[i] + b[i] e c è allineato ma non è b.)

In tal caso, potrebbe essere utile utilizzare un ramo di fare carichi allineati prima e dopo la divisione di pagina, e combinarle con palignr.

Un errore di sottomissione (~ 15c) è più economico della latenza suddivisa in pagine, ma ritarda tutto (non solo il carico). Quindi potrebbe anche valere non, a seconda dell'hardware e del rapporto di calcolo per l'accesso alla memoria.


Se si scrive una funzione che è di solito chiamato con i puntatori allineati, ha senso utilizzare solo istruzioni load/store non allineati. Qualsiasi prologo per rilevare il disallineamento è solo un overhead aggiuntivo per il caso già allineato e su hardware moderno (Nehalem e più recente), carichi non allineati sull'indirizzo che risultano allineati in fase di esecuzione hanno prestazioni identiche a istruzioni di caricamento allineate. (Ma è necessario AVX per carichi non allineati da piegare in altre istruzioni come operandi di memoria, ad esempio vpxor xmm0, xmm1, [rsi])

Aggiungendo il codice per gestire gli ingressi non allineati, si rallenta il caso allineato comune per accelerare il caso disallineato non comune. Il veloce supporto hardware per carichi/negozi non allineati consente al software di lasciarlo all'hardware per i pochi casi in cui si verifica.

(Se gli ingressi non allineati sono comuni, allora è vale la pena di usare un prologo per allineare il puntatore di ingresso, esp. Se si sta utilizzando carichi AVX. Sequenziale 32B AVX saranno in cache-line diviso ogni altro carico.)

Vedere Agner Fog's Optimizing Assembly guide per ulteriori informazioni e altri collegamenti nel tag wiki .

+0

@ZheyuanLi: Sì, sono curioso di sapere cosa ha permesso il cambiamento del design. Skylake può anche eseguire due passaggi di pagina in parallelo per risolvere due missioni TLB. Questi due fatti possono essere collegati. –

+0

Grazie !. Inoltre, non mi ero reso conto che l'accesso tra le pagine potrebbe avere costi così alti. Quindi questo è sicuramente qualcosa da cercare. –

+1

BTW, Valgrind ha l'opzione --partial-loads-ok = yes che può nascondere i problemi di "lettura non valida" causati da carichi vettoriali quando i dati caricati hanno superato la fine del buffer. –

Problemi correlati