Właśnie próbowałem zoptymalizować konwerter RGB na YUV420. Korzystanie z tabeli przeglądowej spowodowało wzrost prędkości, podobnie jak w przypadku arytmetyki stałoprzecinkowej. Spodziewałem się jednak prawdziwych korzyści za pomocą instrukcji SSE. Moje pierwsze odwiedziny spowodują spowolnienie kodu i po powiązaniu wszystkich operacji są mniej więcej takie same, jak w oryginalnym kodzie. Czy jest coś złego w mojej implementacji lub instrukcje SSE po prostu nie pasują do wykonywanego zadania?SIMD: Dlaczego konwersja kolorów SSE RGB na YUV ma tę samą szybkość, co implementacja C++?
Część oryginalnego kodu następująco:
#define RRGB24YUVCI2_00 0.299
#define RRGB24YUVCI2_01 0.587
#define RRGB24YUVCI2_02 0.114
#define RRGB24YUVCI2_10 -0.147
#define RRGB24YUVCI2_11 -0.289
#define RRGB24YUVCI2_12 0.436
#define RRGB24YUVCI2_20 0.615
#define RRGB24YUVCI2_21 -0.515
#define RRGB24YUVCI2_22 -0.100
void RealRGB24toYUV420Converter::Convert(void* pRgb, void* pY, void* pU, void* pV)
{
yuvType* py = (yuvType *)pY;
yuvType* pu = (yuvType *)pU;
yuvType* pv = (yuvType *)pV;
unsigned char* src = (unsigned char *)pRgb;
/// Y have range 0..255, U & V have range -128..127.
double u,v;
double r,g,b;
/// Step in 2x2 pel blocks. (4 pels per block).
int xBlks = _width >> 1;
int yBlks = _height >> 1;
for(int yb = 0; yb < yBlks; yb++)
for(int xb = 0; xb < xBlks; xb++)
{
int chrOff = yb*xBlks + xb;
int lumOff = (yb*_width + xb) << 1;
unsigned char* t = src + lumOff*3;
/// Top left pel.
b = (double)(*t++);
g = (double)(*t++);
r = (double)(*t++);
py[lumOff] = (yuvType)RRGB24YUVCI2_RANGECHECK_0TO255((int)(0.5 + RRGB24YUVCI2_00*r + RRGB24YUVCI2_01*g + RRGB24YUVCI2_02*b));
u = RRGB24YUVCI2_10*r + RRGB24YUVCI2_11*g + RRGB24YUVCI2_12*b;
v = RRGB24YUVCI2_20*r + RRGB24YUVCI2_21*g + RRGB24YUVCI2_22*b;
/// Top right pel.
b = (double)(*t++);
g = (double)(*t++);
r = (double)(*t++);
py[lumOff+1] = (yuvType)RRGB24YUVCI2_RANGECHECK_0TO255((int)(0.5 + RRGB24YUVCI2_00*r + RRGB24YUVCI2_01*g + RRGB24YUVCI2_02*b));
u += RRGB24YUVCI2_10*r + RRGB24YUVCI2_11*g + RRGB24YUVCI2_12*b;
v += RRGB24YUVCI2_20*r + RRGB24YUVCI2_21*g + RRGB24YUVCI2_22*b;
lumOff += _width;
t = t + _width*3 - 6;
/// Bottom left pel.
b = (double)(*t++);
g = (double)(*t++);
r = (double)(*t++);
py[lumOff] = (yuvType)RRGB24YUVCI2_RANGECHECK_0TO255((int)(0.5 + RRGB24YUVCI2_00*r + RRGB24YUVCI2_01*g + RRGB24YUVCI2_02*b));
u += RRGB24YUVCI2_10*r + RRGB24YUVCI2_11*g + RRGB24YUVCI2_12*b;
v += RRGB24YUVCI2_20*r + RRGB24YUVCI2_21*g + RRGB24YUVCI2_22*b;
/// Bottom right pel.
b = (double)(*t++);
g = (double)(*t++);
r = (double)(*t++);
py[lumOff+1] = (yuvType)RRGB24YUVCI2_RANGECHECK_0TO255((int)(0.5 + RRGB24YUVCI2_00*r + RRGB24YUVCI2_01*g + RRGB24YUVCI2_02*b));
u += RRGB24YUVCI2_10*r + RRGB24YUVCI2_11*g + RRGB24YUVCI2_12*b;
v += RRGB24YUVCI2_20*r + RRGB24YUVCI2_21*g + RRGB24YUVCI2_22*b;
/// Average the 4 chr values.
int iu = (int)u;
int iv = (int)v;
if(iu < 0) ///< Rounding.
iu -= 2;
else
iu += 2;
if(iv < 0) ///< Rounding.
iv -= 2;
else
iv += 2;
pu[chrOff] = (yuvType)(_chrOff + RRGB24YUVCI2_RANGECHECK_N128TO127(iu/4));
pv[chrOff] = (yuvType)(_chrOff + RRGB24YUVCI2_RANGECHECK_N128TO127(iv/4));
}//end for xb & yb...
}//end Convert.
A oto wersję przy użyciu SSE
const float fRRGB24YUVCI2_00 = 0.299;
const float fRRGB24YUVCI2_01 = 0.587;
const float fRRGB24YUVCI2_02 = 0.114;
const float fRRGB24YUVCI2_10 = -0.147;
const float fRRGB24YUVCI2_11 = -0.289;
const float fRRGB24YUVCI2_12 = 0.436;
const float fRRGB24YUVCI2_20 = 0.615;
const float fRRGB24YUVCI2_21 = -0.515;
const float fRRGB24YUVCI2_22 = -0.100;
void RealRGB24toYUV420Converter::Convert(void* pRgb, void* pY, void* pU, void* pV)
{
__m128 xmm_y = _mm_loadu_ps(fCOEFF_0);
__m128 xmm_u = _mm_loadu_ps(fCOEFF_1);
__m128 xmm_v = _mm_loadu_ps(fCOEFF_2);
yuvType* py = (yuvType *)pY;
yuvType* pu = (yuvType *)pU;
yuvType* pv = (yuvType *)pV;
unsigned char* src = (unsigned char *)pRgb;
/// Y have range 0..255, U & V have range -128..127.
float bgr1[4];
bgr1[3] = 0.0;
float bgr2[4];
bgr2[3] = 0.0;
float bgr3[4];
bgr3[3] = 0.0;
float bgr4[4];
bgr4[3] = 0.0;
/// Step in 2x2 pel blocks. (4 pels per block).
int xBlks = _width >> 1;
int yBlks = _height >> 1;
for(int yb = 0; yb < yBlks; yb++)
for(int xb = 0; xb < xBlks; xb++)
{
int chrOff = yb*xBlks + xb;
int lumOff = (yb*_width + xb) << 1;
unsigned char* t = src + lumOff*3;
bgr1[2] = (float)*t++;
bgr1[1] = (float)*t++;
bgr1[0] = (float)*t++;
bgr2[2] = (float)*t++;
bgr2[1] = (float)*t++;
bgr2[0] = (float)*t++;
t = t + _width*3 - 6;
bgr3[2] = (float)*t++;
bgr3[1] = (float)*t++;
bgr3[0] = (float)*t++;
bgr4[2] = (float)*t++;
bgr4[1] = (float)*t++;
bgr4[0] = (float)*t++;
__m128 xmm1 = _mm_loadu_ps(bgr1);
__m128 xmm2 = _mm_loadu_ps(bgr2);
__m128 xmm3 = _mm_loadu_ps(bgr3);
__m128 xmm4 = _mm_loadu_ps(bgr4);
// Y
__m128 xmm_res_y = _mm_mul_ps(xmm1, xmm_y);
py[lumOff] = (yuvType)RRGB24YUVCI2_RANGECHECK_0TO255((xmm_res_y.m128_f32[0] + xmm_res_y.m128_f32[1] + xmm_res_y.m128_f32[2]));
// Y
xmm_res_y = _mm_mul_ps(xmm2, xmm_y);
py[lumOff + 1] = (yuvType)RRGB24YUVCI2_RANGECHECK_0TO255((xmm_res_y.m128_f32[0] + xmm_res_y.m128_f32[1] + xmm_res_y.m128_f32[2]));
lumOff += _width;
// Y
xmm_res_y = _mm_mul_ps(xmm3, xmm_y);
py[lumOff] = (yuvType)RRGB24YUVCI2_RANGECHECK_0TO255((xmm_res_y.m128_f32[0] + xmm_res_y.m128_f32[1] + xmm_res_y.m128_f32[2]));
// Y
xmm_res_y = _mm_mul_ps(xmm4, xmm_y);
py[lumOff+1] = (yuvType)RRGB24YUVCI2_RANGECHECK_0TO255((xmm_res_y.m128_f32[0] + xmm_res_y.m128_f32[1] + xmm_res_y.m128_f32[2]));
// U
__m128 xmm_res = _mm_add_ps(
_mm_add_ps(_mm_mul_ps(xmm1, xmm_u), _mm_mul_ps(xmm2, xmm_u)),
_mm_add_ps(_mm_mul_ps(xmm3, xmm_u), _mm_mul_ps(xmm4, xmm_u))
);
float fU = xmm_res.m128_f32[0] + xmm_res.m128_f32[1] + xmm_res.m128_f32[2];
// V
xmm_res = _mm_add_ps(
_mm_add_ps(_mm_mul_ps(xmm1, xmm_v), _mm_mul_ps(xmm2, xmm_v)),
_mm_add_ps(_mm_mul_ps(xmm3, xmm_v), _mm_mul_ps(xmm4, xmm_v))
);
float fV = xmm_res.m128_f32[0] + xmm_res.m128_f32[1] + xmm_res.m128_f32[2];
/// Average the 4 chr values.
int iu = (int)fU;
int iv = (int)fV;
if(iu < 0) ///< Rounding.
iu -= 2;
else
iu += 2;
if(iv < 0) ///< Rounding.
iv -= 2;
else
iv += 2;
pu[chrOff] = (yuvType)(_chrOff + RRGB24YUVCI2_RANGECHECK_N128TO127(iu >> 2));
pv[chrOff] = (yuvType)(_chrOff + RRGB24YUVCI2_RANGECHECK_N128TO127(iv >> 2));
}//end for xb & yb...
}
Jest to jeden z moich pierwszych prób SSE2 więc może ja czegoś brakuje? FYI Ja pracuje na platformie Windows przy użyciu programu Visual Studio 2008.
Cześć Paul, dziękuję za odpowiedź. Zmodyfikowałem wszystkie tablice na 16 bajtów wyrównane teraz i używam _mm_load_ps zamiast _mm_loadu_ps. Jak dotąd nie widzę żadnej zauważalnej różnicy. Co do twojej drugiej sugestii, proszę wybaczyć moją niewiedzę: jak mogę uniknąć przełączania się między skalarem a kodem SIMD? Nie rozumiem, jak mogę się pozbyć skalarnego kodu. – Ralf
@Ralf: To jest trudna część, to znaczy myślenie o SIMD, co możesz zrobić z kodem skalarnym. Najlepiej byłoby załadować dane bezpośrednio do rejestrów SSE z pamięci, a następnie ponownie zorganizować elementy do wymaganego układu, wykonać obliczenia, ponownie zorganizować z powrotem do wymaganego układu wyjściowego, a następnie zapisać bezpośrednio w pamięci z rejestru (rejestrów) SSE . Jeśli masz SSSE3 (aka SSE3.5) lub lepszy, tasowanie elementów, o ile jest to łatwiejsze (PSHUFB) - z SSE3 i wcześniejszymi wersjami jest nadal możliwe, ale trochę trudniejsze, ponieważ dostępne są ograniczone instrukcje shuffle. –
Ok, dzięki Paul, pozwól mi zrobić więcej badań na temat SIMD :) – Ralf