2016-02-09 31 views
8

Mi occupo di elaborazione delle immagini. devo dividere interi a 16 bit SSE vettore per 255.Come dividere l'intero a 16 bit di 255 con SSE?

non posso usare operatore di spostamento come _mm_srli_epi16(), perché 255 non è un multiplo di potenza di 2.

so, naturalmente, che è possibile convertire il numero intero in float, eseguire la divisione e quindi tornare alla conversione in intero.

ma potrebbe qualcuno conosce un'altra soluzione ...

+1

Fa [questo] (http://stackoverflow.com/q/16822757/3959454) aiuto? –

+1

In genere si dividerà per 256 (con arrotondamento anziché con troncamento) - c'è qualche motivo per cui deve essere 255 e non 256? –

+1

Forse questa domanda (http://stackoverflow.com/questions/31575833/fastest-method-of-vectorized-integer-division-by-non-constant-divisor) è interessante anche per te. Quando devi affrontare una divisione intera non costante in futuro, anche la conversione in float è un'opzione veloce. – Youka

risposta

10

C'è un'approssimazione intero di divisione per 255:

inline int DivideBy255(int value) 
{ 
    return (value + 1 + (value >> 8)) >> 8; 
} 

Così con l'utilizzo di SSE2 che sarà del tipo:

inline __m128i DivideI16By255(__m128i value) 
{ 
    return _mm_srli_epi16(_mm_add_epi16(
     _mm_add_epi16(value, _mm_set1_epi16(1)), _mm_srli_epi16(value, 8)), 8); 
} 

per AVX2:

inline __m256i DivideI16By255(__m256i value) 
{ 
    return _mm256_srli_epi16(_mm256_add_epi16(
     _mm256_add_epi16(value, _mm256_set1_epi16(1)), _mm256_srli_epi16(value, 8)), 8); 
} 

Per Altivec (Potenza):

typedef __vector int16_t v128_s16; 
const v128_s16 K16_0001 = {1, 1, 1, 1, 1, 1, 1, 1}; 
const v128_s16 K16_0008 = {8, 8, 8, 8, 8, 8, 8, 8}; 

inline v128_s16 DivideBy255(v128_s16 value) 
{ 
    return vec_sr(vec_add(vec_add(value, K16_0001), vec_sr(value, K16_0008)), K16_0008); 
} 

per Neon (ARM):

inline int16x8_t DivideI16By255(int16x8_t value) 
{ 
    return vshrq_n_s16(vaddq_s16(
     vaddq_s16(value, vdupq_n_s16(1)), vshrq_n_s16(value, 8)), 8); 
} 
+0

Questo è sbagliato per 'value == 65535' e per tutti i numeri negativi (quindi non funziona né per interi a 16 bit firmati né senza segno) –

+1

So che funziona perfettamente per l'alpha blending. Ma non escludo errori in altri casi. – ErmIg

+0

@AntonSavin: ho pubblicato una risposta in base al link all'altra domanda che hai trovato. gcc vettorializza una versione perfettamente accurata con solo un paio di operazioni in più. –

3

GCC ottimizza x/255 con x è unsigned short a DWORD(x * 0x8081) >> 0x17 che può essere ulteriormente semplificata in HWORD(x * 0x8081) >> 7 e infine HWORD((x << 15) + (x << 7) + x) >> 7.

macro SIMD possono assomigliare a questo:

#define MMX_DIV255_U16(x) _mm_srli_pi16(_mm_mulhi_pu16(x, _mm_set1_pi16((short)0x8081)), 7) 
#define SSE2_DIV255_U16(x) _mm_srli_epi16(_mm_mulhi_epu16(x, _mm_set1_epi16((short)0x8081)), 7) 
#define AVX2_DIV255_U16(x) _mm256_srli_epi16(_mm256_mulhi_epu16(x, _mm256_set1_epi16((short)0x8081)), 7) 
6

Se si desidera un risultato esattamente corretto per tutti i casi, seguire il consiglio da Marc Glisse's commento sulla questione Anton legata: SSE integer division?

Usa GNU C nativo sintassi vettore per esprimere la divisione di un vettore per la vostra data scalare, and see what it does:

typedef short vec_s16 __attribute__((vector_size(16))); 

vec_s16 div255(vec_s16 x){ return x/255; } // signed division 

    ; function arg x starts in xmm0 
    vpmulhw xmm1, xmm0, XMMWORD PTR .LC3[rip] ; a vector of set1(0x8081) 
    vpaddw xmm1, xmm1, xmm0 
    vpsraw xmm0, xmm0, 15  ; shift the original 
    vpsraw xmm1, xmm1, 7  ; shift the mulhi-and-add result 
    vpsubw xmm0, xmm1, xmm0 

.LC3: 
     .value -32639 
     .value -32639 
     ; repeated 

a t egli rischio di gonfiore la risposta, eccola di nuovo con intrinseche:

__m128i div255_si128(__m128i x) { 
    __m128i tmp = _mm_mulhi_epi16(x, _mm_set1_epi16(0x8081)); 
    tmp = _mm_add_epi16(tmp, x); // There's no integer FMA that's usable here 
    x = _mm_srai_epi16(x, 15); // broadcast the sign bit 
    tmp = _mm_srai_epi16(tmp, 7); 
    return _mm_sub_epi16(tmp, x); 
} 

In uscita Godbolt, notare che gcc è abbastanza intelligente per utilizzare lo stesso 16B costante in memoria per la set1 e per quella che essa stessa ha generato per div255. AFAIK, funziona come fusione costante di stringhe.