2010-06-28 17 views
7

Jestem bardzo nowy w SIMD/SSE i próbuję wykonać proste filtrowanie obrazu (rozmycie). Poniższy kod filtruje każdy piksel z 8-bitowej szarej bitmapy za pomocą prostego [1 2 1] ważenia w kierunku poziomym. Tworzę sumy 16 pikseli naraz.newbie dla SIMD: proste filtrowanie obrazu

To, co wydaje się bardzo złe w tym kodzie, przynajmniej dla mnie jest to, że jest w nim dużo wstawki/ekstraktu, który nie jest zbyt elegancki i prawdopodobnie spowalnia również wszystko. Czy istnieje lepszy sposób na zawijanie danych z jednego regu do drugiego podczas zmiany?

buf to dane obrazu wyrównane do 16 bajtów. w/H są szerokość i wysokość, wielokrotność 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); 
} 
+0

Chciałbym również docenić ogólnych uwag na temat możliwych ulepszeń kodu. Dzięki! – dietr

Odpowiedz

2

Proponuję zachować sąsiednie piksele w rejestrze SSE. Oznacza to, że zachowaj wynik _mm_slli_si128/_mm_srli_si128 w zmiennej SSE i wyeliminuj wszystkie wstawki i wyciągi. Moje rozumowanie jest takie, że w starszych procesorach instrukcje wstawiania/wyciągania wymagają komunikacji między jednostkami SSE i jednostkami ogólnego przeznaczenia, co jest znacznie wolniejsze niż utrzymywanie obliczeń w SSE, nawet jeśli zostanie ono przeniesione do pamięci podręcznej L1.

Po wykonaniu tej czynności powinny istnieć tylko cztery 16-bitowe zmiany (_mm_slli_si128, _mm_srli_si128, nie licząc przesunięcia dywidendy). Moja sugestia polega na wykonaniu testu porównawczego z kodem, ponieważ do tego czasu twój kod mógł już przekroczyć limit przepustowości pamięci. Oznacza to, że nie możesz już zoptymalizować.

Jeśli obraz jest duży (większy niż rozmiar L2), a wynik nie zostanie wkrótce odczytany, spróbuj użyć funkcji MOVNTDQ (_mm_stream_si128) do zapisania. Według kilku stron internetowych jest to w SSE2, chociaż możesz chcieć dwukrotnie sprawdzić.

SIMD poradnik:

Niektóre strony internetowe SIMD guru:

2

Ten rodzaj działania sąsiedztwa zawsze ból z wiatru, aż SSE3.5 (czyli ssse3) pojawił się i PALIGNR (_mm_alignr_epi8) został wprowadzony .

Jeśli potrzebujesz kompatybilności wstecznej z SSE2/SSE3, możesz napisać równoważną funkcję makr lub inline, która emuluje _mm_alignr_epi8 dla SSE2/SSE3 i która przechodzi do _mm_alignr_epi8, gdy celujesz w SSE3.5/SSE4.

Innym podejściem jest użycie niewyrównanych obciążeń w celu uzyskania przesuniętych danych - jest to względnie kosztowne w starszych procesorach (z grubsza dwa razy opóźnienie i połowa przepustowości wyrównanych obciążeń), ale może to być dopuszczalne w zależności od dużej ilości obliczeń Robić na obciążenie. Ma także tę zaletę, że na obecnych procesorach Intel Core (Core i7) źle ustawione obciążenia nie mają żadnych kar w porównaniu z ładunkami wyrównanymi, więc twój kod będzie dość wydajny na Core i7 i innych.

+0

Zauważyłem już alignr, ale podejrzewam, że chcę być kompatybilny z SSE2. Myślę, że SSE2 to dobry "najniższy wspólny mianownik", jeśli chodzi o SIMD na x86, a jeśli sam SSE2 da mi satysfakcjonujące przyspieszenie, nie zawracam sobie głowy wprowadzaniem czegoś zaawansowanego. – dietr