2014-10-26 23 views
6

Sto cercando di venire con un algoritmo di soglia molto veloce utilizzando SSE per sostituire questo:soglia SSE veloce algoritmo

uint8_t *pSrc, *pDst; 

// Assume pSrc and pDst point to valid data 

// Handle left edge 
*pDst++ = *pSrc++; 

// Likeness filter 
for (uint32_t k = 2; k < width; k++, pSrc++, pDst++) 
    if ((*pDst - *pSrc) * (*pDst - *pSrc) > 100 /*THRESHOLD_SQUARED*/) { 
     *pDst = *pSrc; 
    } 
} 

// Handle right edge 
*pDst++ = *pSrc++; 

Finora ho questo:

const uint8_t THRESHOLD = 10; 

__attribute__((aligned (16))) static const uint8_t mask[16] = { 
    THRESHOLD, THRESHOLD, THRESHOLD, THRESHOLD, 
    THRESHOLD, THRESHOLD, THRESHOLD, THRESHOLD, 
    THRESHOLD, THRESHOLD, THRESHOLD, THRESHOLD, 
    THRESHOLD, THRESHOLD, THRESHOLD, THRESHOLD 
}; 

__m128i xmm1, xmm3, xmm4, xmm5, xmm6, xmm7, xmm8, xmm9; 
xmm1 = _mm_load_si128((__m128i const *)mask); 
xmm6 = _mm_setzero_si128(); 

uint8_t *pSrc, *pDst; 

// Assume pSrc and pDst point to valid data 

// I have other code with another mask for the first 16 entries 

for (uint32_t k = 16; k < (width - 16); k += 16, pSrc += 16, pDst += 16) { 
    xmm3 = _mm_load_si128((__m128i const *)pDst); 
    xmm4 = _mm_load_si128((__m128i const *)pSrc); 
    xmm5 = _mm_unpacklo_epi8(xmm3, xmm6); 
    xmm7 = _mm_unpackhi_epi8(xmm3, xmm6); 
    xmm8 = _mm_unpacklo_epi8(xmm4, xmm6); 
    xmm9 = _mm_unpackhi_epi8(xmm4, xmm6); 
    xmm5 = _mm_sub_epi16(xmm5, xmm8); 
    xmm7 = _mm_sub_epi16(xmm7, xmm9); 
    xmm5 = _mm_abs_epi16(xmm5); 
    xmm7 = _mm_abs_epi16(xmm7); 
    xmm5 = _mm_packs_epi16(xmm5, xmm7); 
    xmm5 = _mm_cmpgt_epi8(xmm5, xmm1); 
    xmm3 = _mm_blendv_epi8(xmm3, xmm4, xmm5); 
    _mm_store_si128((__m128i *)pDst, xmm3); 
} 

// I have other code with another mask for the last 16 entries 

ho idea utilizzando un altro tipo di algoritmo per gestire il valore assoluto della differenza di due valori (principalmente per rimanere in U8 (uchar) spazio):

a' = a >> 1; 
b' = b >> 1; 
diff = (abs(sub(a' - b')) << 1) + ((a^b) & 1); 

Th è necessario prendere 8 istruzioni SSE invece dei precedenti 9 (non includendo eventuali movimenti extra del registro generati dal compilatore) ma non sono sicuro se sia più veloce a causa delle latenze delle dipendenze.

Qualche altro esperto SSE ha suggerimenti migliori (utilizzando fino a SSE 4.2)?

Aggiornamento 1 - Grazie al suggerimento di Yves!

const uint8_t THRESHOLD = 10; 

__attribute__((aligned (16))) static const uint8_t mask[16] = { 
    THRESHOLD, THRESHOLD, THRESHOLD, THRESHOLD, 
    THRESHOLD, THRESHOLD, THRESHOLD, THRESHOLD, 
    THRESHOLD, THRESHOLD, THRESHOLD, THRESHOLD, 
    THRESHOLD, THRESHOLD, THRESHOLD, THRESHOLD 
}; 

__m128i xmm1, xmm3, xmm4, xmm5, xmm6, xmm7; 
xmm1 = _mm_load_si128((__m128i const *)mask); 
xmm6 = _mm_setzero_si128(); 

uint8_t *pSrc, *pDst; 

// Assume pSrc and pDst point to valid data 

// I have other code with another mask for the first 16 entries 

for (uint32_t k = 16; k < (width - 16); k += 16, pSrc += 16, pDst += 16) { 
    xmm3 = _mm_load_si128((__m128i const *)pDst); 
    xmm4 = _mm_load_si128((__m128i const *)pSrc); 
    xmm5 = _mm_subs_epu8(xmm3, xmm4); 
    xmm7 = _mm_subs_epu8(xmm4, xmm3); 
    xmm5 = _mm_adds_epu8(xmm5, xmm7); 
    xmm5 = _mm_subs_epu8(xmm5, xmm1); 
    xmm5 = _mm_cmpeq_epi8(xmm5, xmm6); 
    xmm4 = _mm_blendv_epi8(xmm4, xmm3, xmm5); 
    _mm_store_si128((__m128i *)pDst, xmm4); 
} 

// I have other code with another mask for the last 16 entries 
+0

Come mai è passato il tag da SSE a vettorializzazione? SSE dovrebbe essere uno dei tuoi tag. –

+0

I due frammenti di codice non fanno la stessa cosa (il codice C lascia i valori che non vengono confrontati indefiniti mentre il codice SSE li imposta a zero). Inoltre, quasi certamente la larghezza di banda della memoria è vincolata con un'operazione semplice come la soglia SSE, quindi non aspettatevi molta differenza tra gli algoritmi. – Damon

+1

@Damon, grazie all'istruzione di fusione, i valori Dst vengono mantenuti. –

risposta

6

Esiste una valida alternativa per calcolare la differenza assoluta, sfruttando saturazione aritmetica.

Infatti, la sottrazione satura calcola A - B = Max(A - B, 0), in modo che |A-B| = (A - B) + (B - A).

Diff= _mm_adds_epu8(_mm_subs_epu8(A, B), _mm_subs_epu8(B, A)); 

La somma non viene saturata. In questo modo, stai con 16 x 8 bit senza segno e ottieni un throughput massimo.

+0

Grazie! Avevo un'idea simile 10 minuti fa sotto la doccia. Questo sembra essere esattamente quello che sto cercando. Ci proverò. Voterò non appena la mia reputazione arriva a 15. – ChipK

+0

Se posso: non devi più preoccuparti dell'allocazione del registro, il compilatore farà la cosa giusta per te e potrai usare identificatori significativi e espressioni complesse. –

+0

Funziona alla grande con una modifica aggiuntiva all'algoritmo -_mm_min_epu8 (Diff, 0x7f). Questo è necessario poiché _mm_cmpgt_epi8 utilizza valori firmati. Pubblicherò una versione aggiornata dell'algoritmo più tardi oggi. Questo metodo che hai pubblicato sembra avere ulteriori miglioramenti per altri algoritmi - Non vedo l'ora di provarli! – ChipK

1

Ci sono alcune funzioni utili da Simd libreria:

inline __m128i Combine(__m128i mask, __m128i positive, __m128i negative) 
{ 
    return _mm_or_si128(_mm_and_si128(mask, positive), _mm_andnot_si128(mask, negative)); 
} 

inline __m128i AbsDifferenceU8(__m128i a, __m128i b) 
{ 
    return _mm_sub_epi8(_mm_max_epu8(a, b), _mm_min_epu8(a, b)); 
} 

inline __m128i LesserOrEqual8u(__m128i a, __m128i b) 
{ 
    return _mm_cmpeq_epi8(_mm_min_epu8(a, b), a); 
} 

Così SSE2 ottimizzazione sarà simile a questa:

__m128i t = _mm_set1_epi8(threshold); 
for (uint32_t k = 16; k < width - 16; pSrc += 16, pDst += 16) 
{ 
    __m128i src = _mm_load_si128((__m128i*)pSrc); 
    __m128i dst = _mm_load_si128((__m128i*)pDst); 
    __m128i mask = LesserOrEqual8u(AbsDifferenceU8(src, dst), t); 
    _mm_strore_si128((__m128i*)pDst, Combine(mask, dst, src); 
} 
+0

Questa è un'alternativa interessante. Mi chiedo se ha meglio ILP? La risposta accettata utilizza tre add/sub. Quindi è vincolato dal throughput aggiuntivo. La maggior parte delle CPU Intel può eseguire un ciclo di aggiunta/clock SIMD. La tua soluzione utilizza un add e due min/max. Supponendo che il min/max possa utilizzare un'altra porta, questo avrebbe un throughput più elevato rispetto all'utilizzo di add/sub. –

+0

_mm_cmpgt_epu8 non è un singolo comando di istruzioni. Richiede un addizionale o _mm_min_epu8 o _mm_subs_epu8. Ho provato sia il metodo combine che l'istruzione blend. Non vedo molta differenza (come ho affermato sopra). – ChipK

Problemi correlati