2012-02-16 11 views
8

GCC vector extensions offre un modo piacevole e abbastanza portatile per accedere ad alcune istruzioni SIMD su diverse architetture hardware senza ricorrere a hardware specific intrinsics (o auto-vettorizzazione).Caricamento dati per estensioni vettoriali GCC

Un caso di utilizzo reale, sta calcolando un semplice checksum additivo. L'unica cosa che non è chiara è come caricare in sicurezza i dati in un vettore.

typedef char v16qi __attribute__ ((vector_size(16))); 

static uint8_t checksum(uint8_t *buf, size_t size) 
{ 
    assert(size%16 == 0); 
    uint8_t sum = 0; 

    vec16qi vec = {0}; 
    for (size_t i=0; i<(size/16); i++) 
    { 
     // XXX: Yuck! Is there a better way? 
     vec += *((v16qi*) buf+i*16); 
    } 

    // Sum up the vector 
    sum = vec[0] + vec[1] + vec[2] + vec[3] + vec[4] + vec[5] + vec[6] + vec[7] + vec[8] + vec[9] + vec[10] + vec[11] + vec[12] + vec[13] + vec[14] + vec[15]; 

    return sum; 
} 

Casting un puntatore al tipo di vettore sembra funzionare, ma io sono preoccupato che questo potrebbe esplodere in modo orribile se SIMD hardware si aspetta che i tipi di vettore da allineare correttamente.

L'unica altra opzione che ho pensato è usare un vettore temporaneo e caricare esplicitamente i valori (tramite una memcpy o un assegnamento element-wise), ma nel test questo contrasta la maggior parte dell'uso accelerato delle istruzioni SIMD. Idealmente immagino che si tratti di una generica funzione __builtin_load(), ma nessuna sembra esistere.

Qual è un modo più sicuro di caricare i dati in un vettore che mette in pericolo i problemi di allineamento?

+2

L'esecuzione di questo sulla memoria non allineata su GCC x86_64 causerà un SIGSEGV quando la CPU tenta di caricare la memoria non allineata in un registro SSE. Un'opzione ragionevole sembra essere solo memoria di checksum allineata o utilizzare un ciclo normale per sommare i byte fino al primo limite di 16 byte. – dcoles

+0

Nel tuo codice corrente, il caricamento dei dati in realtà viene compilato correttamente se il compilatore conosce l'input (ma la somma è errata): https://godbolt.org/g/DeR3Qv. Non è così bello senza la conoscenza dell'input: https: // Godbolt.org/g/LxEkhp – ZachB

risposta

0

Si potrebbe utilizzare un inizializzatore per caricare i valori, vale a dire fare

const vec16qi e = { buf[0], buf[1], ... , buf[15] } 

e spero che GCC trasforma questo in un'istruzione di carico SSE. Lo verificherei con un dissomemblatore, però ;-). Inoltre, per prestazioni migliori, si tenta di rendere allineato 16-byte buf e informare il compilatore tramite un attributo aligned. Se è possibile garantire che il buffer di input sia allineato, elaborarlo a byte finché non si raggiunge un limite di 16 byte.

+0

Non penso che sia necessario allineare buf. Sarebbe, se avessimo a che fare con i puntatori. – user1095108

+0

@ user1095108 Vuoi che il compilatore trasformi questo in un'istruzione di caricamento SSE, che è l'equivalente di 'e = * buf' (ma non puoi scriverlo in questo modo perché i tipi non corrispondono). Quindi hai a che fare con i puntatori qui, in realtà. Se il compilatore può dedurre che buf è allineato a 16 byte, può quindi utilizzare un carico allineato, che (pre-ivy-bridge, almeno) è più veloce di un carico non allineato. – fgp

+0

No, avresti a che fare con i puntatori se dovessi trasmettere 'buf' a' vec16qi' dalla mia esperienza. – user1095108

1

Modifica (grazie Peter Cordes) Si può lanciare puntatori:

typedef char v16qi __attribute__ ((vector_size (16), aligned (16))); 

v16qi vec = *(v16qi*)&buf[i]; // load 
*(v16qi*)(buf + i) = vec; // store whole vector 

Questo compila per vmovdqa per caricare e vmovups da memorizzare. Se i dati non sono allineati, impostare aligned (1) per generare vmovdqu. (godbolt)

noti che ci sono anche diversi builtins scopi speciali per il carico e scarico di questi registri (Edit 2):

v16qi vec = _mm_loadu_si128((__m128i*)&buf[i]); // _mm_load_si128 for aligned 
_mm_storeu_si128((__m128i*)&buf[i]), vec); // _mm_store_si128 for aligned 

Sembra essere necessario utilizzare -flax-vector-conversions andare da char s a v16qi con questa funzione.

Consulta anche: C - How to access elements of vector using GCC SSE vector extension
Consulta anche: SSE loading ints into __m128

(Suggerimento:. La frase migliore per google è qualcosa come "gcc carico __m128i")

+1

Apparentemente il modo consigliato per caricare i dati non allineati nei vettori GNU C è con un attributo 'allineato (1) 'quando si dichiara un tipo di vettore e si esegue il cast di puntatori a quel tipo di vettore non allineato. per esempio. 'typedef char __attribute__ ((vector_size (16), aligned (1))) unaligned_byte16;'. Vedi [la fine della mia risposta qui] (http://stackoverflow.com/a/39115055/224132), e i commenti di Marc Glisse su di esso. –

+0

@PeterCordes ringraziamenti! Risposta modificata, molto più semplice. – ZachB

+0

Per estrarre, penso che dovresti usare 'vec [0]'. A quanto ho capito, l'aliasing di puntatori scalari su tipi di vettori non è * ok *. Funziona con 'char *' perché 'char *' è speciale, e consente di fare l'alias di qualsiasi cosa. La trasmissione di un 'int *' a un 'v4si *' non conta nemmeno come aliasing, perché v4si è definito in termini di 'int'. I tipi di Intel intrinsec ('__m128i') possono anche essere alias su altre cose, a causa di un attributo extra:' typedef long long __m128i __attribute__ ((__vector_size__ (16), __may_alias __)); 'Senza may_alias, non puoi tranquillamente' v4si ivec = * (v4si) short_pointer'. L'ho lasciato fuori prima di –

Problemi correlati