2012-04-16 10 views
6

Sto imparando a utilizzare le funzionalità di SIMD riscrivendo la mia libreria di elaborazione di immagini personali utilizzando gli elementi intrinseci del vettore. Una funzione di base è un semplice "matrice +=", cioèSIMD array add per lunghezze arbitrarie di array

void arrayAdd(unsigned char* A, unsigned char* B, size_t n) { 
    for(size_t i=0; i < n; i++) { B[i] += A[i] }; 
} 

Per lunghezze degli array arbitrario, il codice SIMD ovvio (supponendo allineato di 16) è qualcosa di simile:

size_t i = 0; 
__m128i xmm0, xmm1; 
n16 = n - (n % 16); 
for (; i < n16; i+=16) { 
    xmm0 = _mm_load_si128((__m128i*) (A + i)); 
    xmm1 = _mm_load_si128((__m128i*) (B + i)); 
    xmm1 = _mm_add_epi8(xmm0, xmm1); 
    _mm_store_si128((__m128i*) (B + i), xmm1); 
} 
for (; i < n; i++) { B[i] += A[i]; } 

Ma è possibile fare all le aggiunte con le istruzioni SIMD? Ho pensato di provare questo:

__m128i mask = (0x100<<8*(n - n16))-1; 
_mm_maskmoveu_si128(xmm1, mask, (__m128i*) (B + i)); 

per gli elementi aggiuntivi, ma questo si tradurrà in un comportamento non definito? Lo mask dovrebbe garantire che nessun accesso venga effettivamente effettuato oltre i limiti dell'array (credo). L'alternativa è fare prima gli elementi extra, ma poi l'array deve essere allineato da n-n16, che non sembra giusto.

Esiste un altro schema più ottimale di tali cicli vettoriali?

+0

si potrebbe garantire che nel codice le lunghezze degli array sono sempre multipli di 16 byte (anche se forse meno elementi sono effettivamente utilizzati), quindi questo epilogo non viene in su. Ma l'epilogo non è davvero importante in termini di velocità. – Walter

risposta

4

Un'opzione consiste nel riempire l'array con un multiplo di 16 byte. Quindi puoi eseguire il caricamento/aggiunta/archiviazione a 128 bit e semplicemente ignorare i risultati seguendo il punto che ti interessa.

Per gli array di grandi dimensioni, sebbene l'overhead del byte per byte "epilog" sia molto piccolo. Srotolando il ciclo può migliorare le prestazioni di più, qualcosa di simile a:

for (; i < n32; i+=32) { 
    xmm0 = _mm_load_si128((__m128i*) (A + i)); 
    xmm1 = _mm_load_si128((__m128i*) (B + i)); 
    xmm2 = _mm_load_si128((__m128i*) (A + i + 16)); 
    xmm3 = _mm_load_si128((__m128i*) (B + i + 16)); 
    xmm1 = _mm_add_epi8(xmm0, xmm1); 
    xmm3 = _mm_add_epi8(xmm2, xmm3); 
    _mm_store_si128((__m128i*) (B + i), xmm1); 
    _mm_store_si128((__m128i*) (B + i + 16), xmm3); 
} 
// Do another 128 bit load/add/store here if required 

Ma è difficile dire senza fare un po 'di profilazione.

Si potrebbe anche eseguire un carico/archivio non allineato alla fine (presupponendo che siano presenti più di 16 byte) anche se questo probabilmente non farà una grande differenza. Per esempio. se si dispone di 20 byte non un load/store per compensare 0 e un altro carico non allineato/aggiungere/negozio (_mm_storeu_si128, __mm_loadu_si128) per compensare 4.

Si potrebbe usare _mm_maskmoveu_si128, ma è necessario per ottenere la maschera in un registro XMM e il tuo codice di esempio non funzionerà. Probabilmente vorresti impostare il registro della maschera su tutti gli FF e poi usare uno spostamento per allinearlo. Alla fine della giornata, sarà probabilmente più lento del carico/add-on/store non allineato.

Questo sarebbe qualcosa di simile:

mask = _mm_cmpeq_epi8(mask, mask); // Set to all FF's 
mask = _mm_srli_si128(mask, 16-(n%16)); // Align mask 
_mm_maskmoveu_si128(xmm, mask, A + i); 
+0

In pratica metterei le maschere in una tabella di ricerca. Pensi che sarebbe ancora più lento del ciclo "epilog"? –

+0

@reve_etrange: Probabilmente non più lento ma difficile da conoscere senza misurare le due soluzioni. Provaci. –

+0

Gli darò un colpo. Ma è un accesso alla memoria legale? Poiché * qualche * valore di 'mask' potrebbe causare una violazione dei limiti dell'array. –

Problemi correlati