2011-08-25 21 views
5

W nawiązaniu do niektórych poprzednich pytań dotyczących konwersji RGB do RGBA i ARGB BGR, chciałbym przyspieszyć RGB do BGRA konwersji z SSE. Załóżmy maszynę 32-bitową i chcielibyśmy użyć intrinsics. Mam trudności z dopasowaniem buforów źródłowego i docelowego do pracy z rejestrami 128-bitowymi i poszukiwanie innych rozwiązań wektoryzacji.Szybka wektorowy konwersji z RGB do BGRA

Rutynowe być wektorowy jest następujący ...

void RGB8ToBGRX8(int w, const void *in, void *out) 
    { 
     int i; 
     int width = w; 
     const unsigned char *src= (const unsigned char*) in; 
     unsigned int *dst= (unsigned int*) out; 
     unsigned int invalue, outvalue; 

     for (i=0; i<width; i++, src+=3, dst++) 
     { 
       invalue = src[0]; 
       outvalue = (invalue<<16); 
       invalue = src[1]; 
       outvalue |= (invalue<<8); 
       invalue = src[2]; 
       outvalue |= (invalue); 
       *dst = outvalue | 0xff000000; 
     } 
     } 

Ta rutyna przyzwyczaja primarly dla dużych tekstur (512kB), więc jeśli mogę parallelize niektóre operacje, może być korzystne dla procesu więcej pikseli w podróży. Oczywiście będę musiał profilować. :)

Edit:

Moje argumenty kompilacja ...

gcc -O2 main.c 
+1

Czy używasz flagi optymalizacji dla swojego kompilatora (który?)? Kompilator często lepiej zoptymalizuje kod, bez jego wprowadzania. Które dane porównawcze zebrałeś? –

+0

Nie jest to odpowiedź SSE, ale czy próbowałeś rozwinąć swoją pętlę 4 razy, tak aby wejście zawsze zaczynało się od wyrównanego adresu? Następnie możesz odczytać dane wejściowe słowa maszynowego, a nie na przemian, ze specjalnym przesuwaniem i maskowaniem dla każdej względnej pozycji piksela źródłowego. Jak wspomina Dana, warto zobaczyć, jak dobrze kompilator wykonuje wyniki na wysokich poziomach optymalizacji (sprawdź wygenerowany kod asemblera, oprócz testów porównawczych), ale wątpię, czy będzie wystarczająco agresywny, aby rozwinąć pętlę _i_ podzieli punkt wejścia zgodnie z wyrównanie 'in' wszystko samo. –

+0

Świetne pytania. To po prostu "O2" (NIE O3) z GCC4.6. Mój przypadek testowy to przebieg iteracji 10K z 512 jako rozpiętością "szerokości". Dzięki za wspaniałe odpowiedzi! – Rev316

Odpowiedz

8

Jest to przykład zastosowania wewnętrznej samoistności SSE3 do wykonania żądanej operacji. Wskaźniki wejściowe i wyjściowe muszą być wyrównane do 16 bajtów i działają na bloku 16 pikseli na raz.

Nie sądzę jednak, aby uzyskać znaczące zwiększenie prędkości. Operacje wykonywane na pikselach są tak proste, że dominuje przepustowość pamięci.

#include <tmmintrin.h> 

/* in and out must be 16-byte aligned */ 
void rgb_to_bgrx_sse(unsigned w, const void *in, void *out) 
{ 
    const __m128i *in_vec = in; 
    __m128i *out_vec = out; 

    w /= 16; 

    while (w-- > 0) { 
     /*    0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 
     * in_vec[0] Ra Ga Ba Rb Gb Bb Rc Gc Bc Rd Gd Bd Re Ge Be Rf 
     * in_vec[1] Gf Bf Rg Gg Bg Rh Gh Bh Ri Gi Bi Rj Gj Bj Rk Gk 
     * in_vec[2] Bk Rl Gl Bl Rm Gm Bm Rn Gn Bn Ro Go Bo Rp Gp Bp 
     */ 
     __m128i in1, in2, in3; 
     __m128i out; 

     in1 = in_vec[0]; 

     out = _mm_shuffle_epi8(in1, 
      _mm_set_epi8(0xff, 9, 10, 11, 0xff, 6, 7, 8, 0xff, 3, 4, 5, 0xff, 0, 1, 2)); 
     out = _mm_or_si128(out, 
      _mm_set_epi8(0xff, 0, 0, 0, 0xff, 0, 0, 0, 0xff, 0, 0, 0, 0xff, 0, 0, 0)); 
     out_vec[0] = out; 

     in2 = in_vec[1]; 

     in1 = _mm_and_si128(in1, 
      _mm_set_epi8(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0)); 
     out = _mm_and_si128(in2, 
      _mm_set_epi8(0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)); 
     out = _mm_or_si128(out, in1); 
     out = _mm_shuffle_epi8(out, 
      _mm_set_epi8(0xff, 5, 6, 7, 0xff, 2, 3, 4, 0xff, 15, 0, 1, 0xff, 12, 13, 14)); 
     out = _mm_or_si128(out, 
      _mm_set_epi8(0xff, 0, 0, 0, 0xff, 0, 0, 0, 0xff, 0, 0, 0, 0xff, 0, 0, 0)); 
     out_vec[1] = out; 

     in3 = in_vec[2]; 
     in_vec += 3; 

     in2 = _mm_and_si128(in2, 
      _mm_set_epi8(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0)); 
     out = _mm_and_si128(in3, 
      _mm_set_epi8(0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)); 
     out = _mm_or_si128(out, in2); 
     out = _mm_shuffle_epi8(out, 
      _mm_set_epi8(0xff, 1, 2, 3, 0xff, 14, 15, 0, 0xff, 11, 12, 13, 0xff, 8, 9, 10)); 
     out = _mm_or_si128(out, 
      _mm_set_epi8(0xff, 0, 0, 0, 0xff, 0, 0, 0, 0xff, 0, 0, 0, 0xff, 0, 0, 0)); 
     out_vec[2] = out; 

     out = _mm_shuffle_epi8(in3, 
      _mm_set_epi8(0xff, 13, 14, 15, 0xff, 10, 11, 12, 0xff, 7, 8, 9, 0xff, 4, 5, 6)); 
     out = _mm_or_si128(out, 
      _mm_set_epi8(0xff, 0, 0, 0, 0xff, 0, 0, 0, 0xff, 0, 0, 0, 0xff, 0, 0, 0)); 
     out_vec[3] = out; 

     out_vec += 4; 
    } 
} 
2

nie mam pełnego zrozumienia co prosisz, a ja z niecierpliwością czeka na właściwą odpowiedź na twoje pytanie. W międzyczasie wymyśliłem implementację, która jest średnio 8 do 10% szybsza. Używam Win7 64bit, używając VS2010, kompilując z C++ do wydania z szybką opcją.

#pragma pack(push, 1) 
    struct RGB { 
     unsigned char r, g, b; 
    }; 

    struct BGRA { 
     unsigned char b, g, r, a; 
    }; 
#pragma pack(pop) 

    void RGB8ToBGRX8(int width, const void* in, void* out) 
    { 
     const RGB* src = (const RGB*)in; 
     BGRA* dst = (BGRA*)out; 
     do {   
      dst->r = src->r; 
      dst->g = src->g; 
      dst->b = src->b; 
      dst->a = 0xFF; 
      src++; 
      dst++; 
     } while (--width); 
    } 

To może lub nie może pomóc, ale mam nadzieję, że tak. Proszę nie głosuj na mnie, jeśli nie, po prostu próbuję to przenieść.

Moja motywacja do korzystania z structs to umożliwienie kompilatorowi, aby jak najskuteczniej przesuwał wskaźniki src i dst. Inną motywacją jest ograniczenie liczby operacji arytmetycznych.

+0

Bez obaw Jack! Jeśli mógłbyś wyjaśnić, której części możesz nie rozumieć, mogę spróbować rozwinąć. :) – Rev316

+0

Co masz na myśli mówiąc o korzystaniu z SSE? Myślę, że oznacza to instruowanie kompilatora, aby używał określonej techniki (technik) optymalizacji, a jeśli tak, to może nie warto jej w ogóle korygować ręcznie. Mówisz też, że chciałbyś użyć intrinsics, co masz na myśli? Jednak mam dobrą znajomość zrównoleglania. – Jack

+0

Oh. Chodziło mi o wektoryzację wektoryzacji za pomocą SSE2/3 lub SSSEE. Głównie opoinowania/maskowania, ponieważ widziałem eleganckie rozwiązania z innymi konwersjami obrazów. Teraz wiem, że GCC4.x ma kilka flag kompilacji, które tutaj pomagają, ale nie jestem pewien, który z nich i/lub czy jest lepszy. Może twoja ekspertyza byłaby pomocna tutaj. – Rev316

2

Osobiście przekonałem się, że wdrożenie poniższego daje mi najlepszy wynik do konwersji BGR-24 na ARGB-32.

Ten kod działa w około 8,8 ms na obrazie, podczas gdy 128-bitowy kod wektoryzacji przedstawiony powyżej ma wartość 14.5ms na obraz.

void PixelFix(u_int32_t *buff,unsigned char *diskmem) 
{ 
    int i,j; 
    int picptr, srcptr; 
    int w = 1920; 
    int h = 1080; 

    for (j=0; j<h; j++) { 
     for (i=0; i<w; i++) { 
      buff[picptr++]=(diskmem[srcptr]<<24) | (diskmem[srcptr+1]<<16) | diskmem[srcptr+2]<<8 | 0xff; 
      srcptr+=3; 
     } 
    } 
} 

Poprzednio używałem tej procedury (około 13,2 ms na obraz). Tutaj buff to niepodpisany znak *.

for (j=0; j<h; j++) { 
    int srcptr = (h-j-1)*w*3; // remove if you don't want vertical flipping 
    for (i=0; i<w; i++) { 
     buff[picptr+3]=diskmem[srcptr++]; // b 
     buff[picptr+2]=diskmem[srcptr++]; // g 
     buff[picptr+1]=diskmem[srcptr++]; // r 
     buff[picptr+0]=255;    // a 
     picptr+=4; 
    } 
} 

Uruchomienie 2012 MacMini 2.6ghz/i7.

+0

Co więcej, warto zajrzeć do najnowszego interfejsu API konwersji VImage firmy Apple ..., w szczególności rutyny takie jak "vImageConvert_RGB888toARGB8888" do konwersji z 24-bitowego RGB na 32-bitowy ARGB (lub BGRA). https://developer.apple.com/library/mac/documentation/Performance/Reference/vImage_conversion/Reference/reference.html#//apple_ref/c/func/vImageConvert_RGB888toARGB8888 – zzyzy

2

Ummm ... używanie vImageConvert_RGB888toARGB8888 jest BARDZO BARDZO szybkie (przyspieszenie 15X).

kod

Przede PixelFix (≈6ms na zdjęcie, teraz na nowszym sprzęcie)


  1. 6.373520 ms
  2. 6.383363 ms
  3. 6.413560 ms
  4. 6.278606 ms
  5. 6,293607 ms
  6. 6.368118 ms
  7. 6,338904 MS
  8. 6.389385 ms
  9. 6,365495 MS

Korzystanie vImageConvert_RGB888toARGB888 gwintowane (na nowszych sprzętu)


  1. 0.563649 MS
  2. 0,400387 MS
  3. 0.375198 MS
  4. 0,360898 ms
  5. 0,391278 ms
  6. 0,396797 ms
  7. 0.405534 ms
  8. 0.386495 ms
  9. 0.367621 ms

Potrzebujesz więcej powiedzieć?

+1

Jeden nawiązanie ... używając jednowątkowy 128-bitowy kod wektorowy "rgb_to_bgrx_sse" powyżej dał wyniki w zakresie 11 ms dla buforów I/O o tym samym rozmiarze. vImage jest tutaj wyraźnym zwycięzcą. – zzyzy