Domanda

Sono molto nuovo per SIMD / SSE e sto cercando di fare qualche semplice immagine di filtraggio (sfocatura). Il seguente codice filtri ogni pixel di una bitmap grigio 8 bit con una semplice [1 2 1] pesando in direzione orizzontale. Sto creando somme di 16 pixel alla volta.

Ciò che sembra molto male su questo codice, almeno per me, è che c'è un sacco di inserimento / estratto in esso, che non è molto elegante e probabilmente rallenta tutto giù pure. C'è un modo migliore per avvolgere i dati da un reg in un altro quando si cambia?

buf è i dati di immagine, di 16 byte allineati. w / h sono larghezza ed altezza, multipli di 16.

__m128i *p = (__m128i *) buf;
__m128i cur1, cur2, sum1, sum2, zeros, tmp1, tmp2, saved;
zeros = _mm_setzero_si128();
short shifted, last = 0, next;

// preload first row
cur1 = _mm_load_si128(p);
for (x = 1; x < (w * h) / 16; x++) {
    // unpack
    sum1 = sum2 = saved = cur1;
    sum1 = _mm_unpacklo_epi8(sum1, zeros);
    sum2 = _mm_unpackhi_epi8(sum2, zeros);
    cur1 = tmp1 = sum1;
    cur2 = tmp2 = sum2;
    // "middle" pixel
    sum1 = _mm_add_epi16(sum1, sum1);
    sum2 = _mm_add_epi16(sum2, sum2);
    // left pixel
    cur2 = _mm_slli_si128(cur2, 2);
    shifted = _mm_extract_epi16(cur1, 7);
    cur2 = _mm_insert_epi16(cur2, shifted, 0);
    cur1 = _mm_slli_si128(cur1, 2);
    cur1 = _mm_insert_epi16(cur1, last, 0);
    sum1 = _mm_add_epi16(sum1, cur1);
    sum2 = _mm_add_epi16(sum2, cur2);
    // right pixel
    tmp1 = _mm_srli_si128(tmp1, 2);
    shifted = _mm_extract_epi16(tmp2, 0);
    tmp1 = _mm_insert_epi16(tmp1, shifted, 7);
    tmp2 = _mm_srli_si128(tmp2, 2);
    // preload next row
    cur1 = _mm_load_si128(p + x);
    // we need the first pixel of the next row for the "right" pixel
    next = _mm_extract_epi16(cur1, 0) & 0xff;
    tmp2 = _mm_insert_epi16(tmp2, next, 7);
    // and the last pixel of last row for the next "left" pixel
    last = ((uint16_t) _mm_extract_epi16(saved, 7)) >> 8;
    sum1 = _mm_add_epi16(sum1, tmp1);
    sum2 = _mm_add_epi16(sum2, tmp2);
    // divide
    sum1 = _mm_srli_epi16(sum1, 2);
    sum2 = _mm_srli_epi16(sum2, 2);
    sum1 = _mm_packus_epi16(sum1, sum2);
    mm_store_si128(p + x - 1, sum1);
}
È stato utile?

Soluzione

Suggerisco di tenere i pixel adiacenti sul registro SSE. Cioè, mantenere il risultato del _mm_slli_si128 / _mm_srli_si128 in una variabile SSE, ed eliminare tutte dell'inserto ed estratto. Il mio ragionamento è che in vecchie CPU, le istruzioni di inserimento / estratto di esigere la comunicazione tra le unità SSE e le unità di uso generale, che è molto più lento di mantenere il calcolo all'interno di SSE, anche se si riversa alla cache L1.

Quando questo è fatto, non ci dovrebbe essere solo quattro turni 16 bit (_mm_slli_si128, _mm_srli_si128, senza contare il passaggio divisone ). Il mio suggerimento è quello di fare un punto di riferimento con il codice, perché da quel momento il codice potrebbe avere già raggiunto il limite di larghezza di banda di memoria .. il che significa che non è possibile ottimizzare più.

Se l'immagine è grande (più grande di dimensioni L2) e l'uscita non verrà letto presto, provare l'uso MOVNTDQ (_mm_stream_si128) per la scrittura di nuovo. Secondo diversi siti web che è in SSE2, anche se si potrebbe desiderare di doppio controllo.

SIMD tutorial:

Alcuni siti web SIMD Guru:

Altri suggerimenti

Questo tipo di operazione quartiere era sempre un dolore con SSE, fino SSE3.5 (aka SSSE3) è arrivato, e PALIGNR (_mm_alignr_epi8) è stato introdotto.

Se avete bisogno di compatibilità con SSE2 / SSE3, però, è possibile scrivere una macro equivalente o una funzione inline che emula _mm_alignr_epi8 per SSE2 / SSE3 e che scende fino al _mm_alignr_epi8 quando rivolte SSE3.5 / SSE4.

Un altro approccio è quello di utilizzare i carichi non allineati per ottenere i dati spostati - questo è relativamente costoso su CPU più anziani (circa il doppio la latenza e la metà del rendimento dei carichi allineati), ma questo può essere accettabile a seconda molto molto calcolo si sta facendo per carico. Essa ha anche il vantaggio che sulla corrente CPU Intel (Core i7) i carichi non allineati hanno nessuna penalità rispetto ai carichi allineati, in modo che il codice sarà abbastanza efficiente sul Core i7 et al .

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top