2016-11-17 21 views
-1

멀티미디어 확장과 병렬 처리 할 수있는 완벽한 프로그램을 프로그래밍하고 있습니다. 이 프로그램은 이미지를 변환하는 것으로 구성되어 있으므로 매트릭스로 가서 각 픽셀을 수정합니다. 이동 속도가 더 빠르면 멀티미디어 확장을 사용합니다.avx 느린 다음 sse 멀티미디어 확장

처음에는 SSE3 확장을 사용하여 2.5 속도 향상을 얻었습니다. 다음으로 AVX 확장 (Double size vector)을 사용하기 위해 sse 알고리즘을 확장하여 프로그래밍했지만 SSE3에 대한 이득은 얻지 못했습니다. SSE로 프로그램을 실행하는 시간은 AVX와 동일합니다. 여기 는 SSE 각각 AVX에 대한 코드의 요약이다 : 당신은 SSE와 AVX 모두 동일하지만 마지막이 볼 수 있듯이

 for(i=0; i<lim; i+=12) { //tam vale n*n o n*n-12 dependiendo de si n*n es multiplo de 12. 12 ya que 3componentes*4pixeles(4 tamvector) 
      vectorR = _mm_set_ps(matrix[i+9], matrix[i+6], matrix[i+3], matrix[i]); 
      vectorG = _mm_set_ps(matrix[i+10], matrix[i+7], matrix[i+4], matrix[i+1]); 
      vectorB = _mm_set_ps(matrix[i+11], matrix[i+8], matrix[i+5], matrix[i+2]); 

      calcular_coeficientes_sse3(Ycoef0, Ycoef1, Ycoef2, Ucoef0, Vcoef0, vectorR, vectorG, vectorB, 
            values0, values255, values128, &vectorY, &vectorU, &vectorV); 

      _mm_store_ps(&y_aux[0], vectorY); 
      _mm_store_ps(&u_aux[0], vectorU); 
      _mm_store_ps(&v_aux[0], vectorV); 

      //Colocamos los datos en la matriz 
      //PIXEL 1 
      matrix[i] = y_aux[0]; 
      matrix[i+1]=u_aux[0]; 
      matrix[i+2]=v_aux[0]; 
      //PIXEL 2 
      matrix[i+3]=y_aux[1]; 
      matrix[i+4]=u_aux[1]; 
      matrix[i+5]=v_aux[1]; 
      //PIXEL 3 
      matrix[i+6] = y_aux[2];; 
      matrix[i+7]=u_aux[2]; 
      matrix[i+8]=v_aux[2]; 
      //PIXEL 4 
      matrix[i+9]=y_aux[3]; 
      matrix[i+10]=u_aux[3]; 
      matrix[i+11]=v_aux[3]; 
    } 

for(i=0; i<lim; i+=24) { //Vamos de 8 en 8 pixeles 
      vectorR = _mm256_set_ps(matrix[i+21], matrix[i+18], matrix[i+15] ,matrix[i+12],matrix[i+9], matrix[i+6], matrix[i+3], matrix[i]); 
      vectorG = _mm256_set_ps(matrix[i+22], matrix[i+19], matrix[i+16], matrix[i+13], matrix[i+10], matrix[i+7], matrix[i+4], matrix[i+1]); 
      vectorB = _mm256_set_ps(matrix[i+23], matrix[i+20], matrix[i+17], matrix[i+14], matrix[i+11], matrix[i+8], matrix[i+5], matrix[i+2]); 

      calcular_coeficientes_avx(Ycoef0, Ycoef1, Ycoef2, Ucoef0, Vcoef0, vectorR, vectorG, vectorB, 
            values0, values255, values128, &vectorY, &vectorU, &vectorV); 

      _mm256_store_ps(&y_aux[0], vectorY); 
      _mm256_store_ps(&u_aux[0], vectorU); 
      _mm256_store_ps(&v_aux[0], vectorV); 

      //Colocamos los datos en la matriz 
      //PIXEL 1 
      matrix[i] = y_aux[0]; 
      matrix[i+1]=u_aux[0]; 
      matrix[i+2]=v_aux[0]; 
      //PIXEL 2 
      matrix[i+3]=y_aux[1]; 
      matrix[i+4]=u_aux[1]; 
      matrix[i+5]=v_aux[1]; 
      //PIXEL 3 
      matrix[i+6] = y_aux[2];; 
      matrix[i+7]=u_aux[2]; 
      matrix[i+8]=v_aux[2]; 
      //PIXEL 4 
      matrix[i+9]=y_aux[3]; 
      matrix[i+10]=u_aux[3]; 
      matrix[i+11]=v_aux[3]; 
      //PIXEL 5 
      matrix[i+12]=y_aux[4]; 
      matrix[i+13]=u_aux[4]; 
      matrix[i+14]=v_aux[4]; 
      //PIXEL 6 
      matrix[i+15]=y_aux[5]; 
      matrix[i+16]=u_aux[5]; 
      matrix[i+17]=v_aux[5]; 
      //PIXEL 7 
      matrix[i+18]=y_aux[6]; 
      matrix[i+19]=u_aux[6]; 
      matrix[i+20]=v_aux[6]; 
      //PIXEL 8 
      matrix[i+21]=y_aux[7]; 
      matrix[i+22]=u_aux[7]; 
      matrix[i+23]=v_aux[7]; 
    } 

void calcular_coeficientes_sse3(__m128 Ycoef0, __m128 Ycoef1, __m128 Ycoef2, __m128 Ucoef0, __m128 Vcoef0, __m128 vectorR, 
          __m128 vectorG, __m128 vectorB, __m128 values0, __m128 values255, __m128 values128, 
            __m128 *vectorY, __m128 *vectorU, __m128 *vectorV) { 

    //CALCULO DE Y3, Y2, Y1, Y0 (Cuatro píxeles consecutivos) 
                //PRIMERA VUELta 
    __m128 valores1 = _mm_mul_ps(Ycoef0, vectorR); // valores1 = (0.299*R[3], 0.299*R[2], 0.299*R[1], 0.299*R[0]) 
    __m128 valores2 = _mm_mul_ps(Ycoef1, vectorG); // valores2 = (0.587*G[3], 0.587*G[2], 0.587*G[1], 0.587*G[0]) 
    __m128 valores3 = _mm_mul_ps(Ycoef2, vectorB); // valores3 = (0.114*B[3], 0.114*B[2], 0.114*B[1], 0.114*B[0]); 
    valores1 = _mm_add_ps(valores1, valores2);  // valores1 = (0.299*R[3] + 0.587*G[3], 0.299*R[2] + 0.587*G[2], 0.299*G[1]+ ..., ...) 
    *vectorY = _mm_add_ps(valores1, valores3);  // vectorY = (Y[3], Y[2], Y[1], Y[0]) 
    *vectorY = _mm_floor_ps(*vectorY); 
    //Calculo de U3, U2, U1, U0 
    //B-Y 
    valores1 = _mm_sub_ps(vectorB, *vectorY);  // valores1 = (B[3]-Y[3], B[2]-Y[2], B[1]-Y[1], B[0]-Y[0]) 
    valores1 = _mm_mul_ps(Ucoef0, valores1);  // valores1 = (0.492*(B[3]-Y[3]), 0.492*(B[2]-Y[2]), 0.492*(B[1]-Y[1]), 0.492*(...)) 
    *vectorU = _mm_add_ps(valores1, values128); // vectorU = (U[3], U[2], U[1], U[0]) 
    //CALCULO DE V3, V2, V1, V0 
    // R-Y 
    valores1 = _mm_sub_ps(vectorR, *vectorY);  // valores1 = (R[3]-Y[3], R[2]-Y[2], R[1]-Y[1], R[0]-Y[0]) 
    valores1 = _mm_mul_ps(Vcoef0, valores1);  // valores1 = (0.877*(R[3]-Y[3]), 0.877*(R[2]-Y[2]), 0.877*(R[1]-Y[1]), 0.877*(...)) 
    valores1 = _mm_add_ps(valores1, values128); // valores1 = (0.877*(R[3]-Y[3]) + 128, 0.877*(R[2]-Y[2]) + 128, ..., ...) 
    //valores1 pueden necesitar saturacion. 

    //SATURACIONES a 0 
    //Para evitar hacer comparaciones cogemos el mayor entre 0 y el valor V[i]: 
                  // Si V[i] > 0 se queda con V[i] pues es mayor que 0. 
                  // Si V[i] < 0 se queda con 0 pues es mayor que un número negativo. 
    valores1 = _mm_max_ps(valores1, values0);  // valores1 = (max(0.877*(R[3]-Y[3]) + 128,0), ..., ..., ...) 

    // SATURACIONES a 255 
    //Para evitar hacer comparacion cogemos el menor entre 255 y el valor V[i] 
                  // Si V[i] < 255 entonces se queda con el menor, V[i] 
                  // Si V[i] > 255 entonces se queda con el menor, 255. 
    *vectorV = _mm_min_ps(valores1, values255); //vectorV = (V[3], V[2], V[1], V[0]) 

    //NOTA: Al estar las operaciones implementadas en hardware se hacen las operaciones max y min en un 1 ciclo. 
    //por lo que solo en dos ciclos comprobamos la saturacion de 4 valores V. 

    return; //El procedimiento termina devolviendo vectorY, vectorU y vectorV 

}

void calcular_coeficientes_avx(__m256 Ycoef0, __m256 Ycoef1, __m256 Ycoef2, __m256 Ucoef0, __m256 Vcoef0, __m256 vectorR, 
          __m256 vectorG, __m256 vectorB, __m256 values0, __m256 values255, __m256 values128, 
            __m256 *vectorY, __m256 *vectorU, __m256 *vectorV) { 

    //CALCULO DE Y7, Y6, Y5, Y4, Y3, Y2, Y1, Y0 (Cuatro píxeles consecutivos) 

    __m256 valores1 = _mm256_mul_ps(Ycoef0, vectorR); 
    __m256 valores2 = _mm256_mul_ps(Ycoef1, vectorG); 
    __m256 valores3 = _mm256_mul_ps(Ycoef2, vectorB); 
    valores1 = _mm256_add_ps(valores1, valores2); 
    *vectorY = _mm256_add_ps(valores1, valores3); 
    *vectorY = _mm256_floor_ps(*vectorY); 
    //Calculo de U7, U6, U5, U4, U3, U2, U1, U0 
    valores1 = _mm256_sub_ps(vectorB, *vectorY); 
    valores1 = _mm256_mul_ps(Ucoef0, valores1); 
    *vectorU = _mm256_add_ps(valores1, values128); 
    //CALCULO DE V7, V6, V5, V4, V3, V2, V1, V0 
    // R-Y 
    valores1 = _mm256_sub_ps(vectorR, *vectorY); 
    valores1 = _mm256_mul_ps(Vcoef0, valores1); 
    valores1 = _mm256_add_ps(valores1, values128); 
    //valores1 pueden necesitar saturacion. 

    //SATURACIONES a 0 
    //Para evitar hacer comparaciones cogemos el mayor entre 0 y el valor V[i]: 


    valores1 = _mm256_max_ps(valores1, values0); 

    // SATURACIONES a 255 
    //Para evitar hacer comparacion cogemos el menor entre 255 y el valor V[i] 
                  // Si V[i] < 255 entonces se queda con el menor, V[i] 
                  // Si V[i] > 255 entonces se queda con el menor, 255. 
    *vectorV = _mm256_min_ps(valores1, values255); //vectorV = (V[3], V[2], V[1], V[0]) 

    //NOTA: Al estar las operaciones implementadas en hardware se hacen las operaciones max y min en un 1 ciclo. 
    //por lo que solo en dos ciclos comprobamos la saturacion de 4 valores V. 

    return; //El procedimiento termina devolviendo vectorY, vectorU y vectorV 

}

확장하고 더 긴 크기의 벡터를 사용하십시오. 왜 같은 실행 시간입니까?

참고 : 나는 AVX 지원 (obviusly)을 사용하는 두 대의 다른 컴퓨터에서 시도했지만 동일한 문제가 있습니다.

대단히 감사합니다.

+2

는 작업 calcular_coeficientes_avx''에서 수행되며,'아직 그 기능이없는 calcular_coeficientes_sse'. 또한 AVX512 태그는 오도 된 것일 수 있습니다. –

+2

'_mm256_set_ps (행렬 [i + 21], 행렬 [i + 18], ...)이 작업을 수행하지 마십시오 SSE에서도 마찬가지입니다 .. 그런 식으로 글을 쓸 때마다 강아지가 죽습니다. – harold

+1

* 두 가지 다른 컴퓨터에서 시도 했었습니다 * : 다른 방법? 인텔 SnB 제품군 또는 두 AMD 모두였습니까? 심지어 * 코드가 잘 작성된 경우 AMD CPU는 많은 이점을 얻지 못하는 경우가 많습니다 256b AVX 사용에서. http://agner.org/optimize/ 및 [x86 태그 위키]의 다른 링크 (http://stackoverflow.com/tags/x86/info)를 참조하십시오. –

답변

2

RGB에서 YUV에 대한 부동 소수점 산술을 사용하면 막대한 잔인한 부분이므로 여기에서 고정 소수점 산술을 사용합니다. 출력 형식은 매우 성가시다. 사용했던 것보다 더 똑똑한 방법을 사용하는 것이 현명한 방법이지만, YUV 데이터로 무엇을하고 싶을 때 무엇을 하든지 다시 성가 시게됩니다. [YYYYYYYY UUUUUUUU VVVVVVVV]와 같은 형식을 사용하는 것이 더 합리적입니다.

이미 사용 가능한 RGB 대 YUV 변환기가 있지만 어쨌든 다른 시도해 보았습니다. 더 많은 대안을 사용할 수 없으며, 대부분은 일치하지 않습니다. 예를 들어 크로마 하위 샘플링과 보통 다른 출력 형식을 사용하는 경우.

정확성이나 성능에 대한 의견을 보내 주시면 (컴파일 여부를 확인하는 것 외에는) 테스트 해 보지 않았습니다.

void toYUV(uint8_t* pixels, int size) 
{ 
    // r g b r g b r g b r g b r g b r | g b r g b r g b 

    short rw = 9798; // (short)round(0.299 * 2^15) 
    short gw = 19234;// (short)round(0.587 * 2^15) 
    short bw = 3736; // (short)round(0.114 * 2^15) 
    char _ = -1; 

    __m128i z = _mm_setzero_si128(); 

    for (int i = 0; i < size; i += 24) 
    { 
    __m128i p0 = _mm_loadu_si128((__m128i*)(pixels + i)); 
    __m128i p1 = _mm_loadl_epi64((__m128i*)(pixels + i + 16)); 

    // widen 
    // w0 = r0 g0 b0 r1 g1 b1 r2 g2 
    __m128i w0 = _mm_unpacklo_epi8(p0, z); 
    // w1 = b2 r3 g3 b3 r4 g4 b4 r5 
    __m128i w1 = _mm_unpackhi_epi8(p0, z); 
    // w2 = g5 b5 r6 g6 b6 r7 g7 b7 
    __m128i w2 = _mm_unpacklo_epi8(p1, z); 

    __m128i zRGBRGBz = _mm_setr_epi16(0, rw, gw, bw, rw, gw, bw, 0); 
    // calculate Y 
    // 0+r0 g0+b0 r1+g1 b1+0 
    __m128i s0 = _mm_madd_epi16(_mm_bslli_si128(w0, 2), zRGBRGBz); 
    // 0+r2 g2+b2 r3+g3 b3+0 
    __m128i s1 = _mm_madd_epi16(_mm_alignr_epi8(w1, w0, 10), zRGBRGBz); 
    // 0+r4 g4+b4 r5+g5 b5+0 
    __m128i s2 = _mm_madd_epi16(_mm_alignr_epi8(w2, w1, 6), zRGBRGBz); 
    // 0+r6 g6+b6 r7+g7 b7+0 
    __m128i s3 = _mm_madd_epi16(_mm_bsrli_si128(w2, 2), zRGBRGBz); 
    // the math works out to make the Y's in [0 .. 254] after scaling back, so 
    // y0 = y0 0 y1 0 y2 0 y3 0 
    // y1 = y4 0 y5 0 y6 0 y7 0 
    __m128i y0 = _mm_srli_epi32(_mm_hadd_epi32(s0, s1), 15); 
    __m128i y1 = _mm_srli_epi32(_mm_hadd_epi32(s2, s3), 15); 
    __m128i y = _mm_packus_epi32(y0, y1); 
    y = _mm_packus_epi16(y, y); 

    // calculate U 
    // todo: do smarter shuffles? 
    // b0 = 0 b0 0 b1 0 b2 0 b3 
    __m128i b0 = _mm_or_si128(
     _mm_shuffle_epi8(w0, _mm_setr_epi8(_, _, 4, 5, _, _, 10, 11, _, _, _, _, _, _, _, _)), 
     _mm_shuffle_epi8(w1, _mm_setr_epi8(_, _, _, _, _, _, _, _, _, _, 0, 1, _, _, 6, 7))); 
    // b1 = 0 b4 0 b5 0 b6 0 b7 
    __m128i b1 = _mm_or_si128(
     _mm_shuffle_epi8(w1, _mm_setr_epi8(_, _, 13, 14, _, _, _, _, _, _, _, _, _, _, _, _)), 
     _mm_shuffle_epi8(w2, _mm_setr_epi8(_, _, _, _, _, _, 2, 3, _, _, 8, 9, _, _, 14, 15))); 
    // b - y in [-225 .. 226] 
    // u = 18492 * (b - y) >> 15 -> (18492 * b + -18492 * y) >> 15 
    // .. so u in [-128 .. 127] 
    short us = 18492; 
    __m128i u0 = _mm_madd_epi16(_mm_or_si128(b0, y0), _mm_setr_epi16(-us, us, -us, us, -us, us, -us, us)); 
    u0 = _mm_srai_epi32(u0, 15); 
    __m128i u1 = _mm_madd_epi16(_mm_or_si128(b1, y1), _mm_setr_epi16(-us, us, -us, us, -us, us, -us, us)); 
    u1 = _mm_srai_epi32(u1, 15); 
    // pack to sbytes 
    __m128i u = _mm_packs_epi32(u0, u1); 
    u = _mm_packs_epi16(u, u); 

    // calculate V 
    // todo: do smarter shuffles? 
    // r0 = 0 r0 0 r1 0 r2 0 r3 
    __m128i r0 = _mm_or_si128(
     _mm_shuffle_epi8(w0, _mm_setr_epi8(_, _, 0, 1, _, _, 6, 7, _, _, 12, 13, _, _, _, _)), 
     _mm_shuffle_epi8(w1, _mm_setr_epi8(_, _, _, _, _, _, _, _, _, _, _, _, _, _, 2, 3))); 
    // r1 = 0 r4 0 r5 0 r6 0 r7 
    __m128i r1 = _mm_or_si128(
     _mm_shuffle_epi8(w1, _mm_setr_epi8(_, _, 8, 9, _, _, 14, 15, _, _, _, _, _, _, _, _)), 
     _mm_shuffle_epi8(w2, _mm_setr_epi8(_, _, _, _, _, _, _, _, _, _, 4, 5, _, _, 10, 11))); 
    // r - y in [-178 .. 179] 
    // v = 23372 * (r - y) >> 15 -> (23372 * r + -23372 * y) >> 15 
    // .. so v in [-128 .. 127] 
    short vs = 23372; 
    __m128i v0 = _mm_madd_epi16(_mm_or_si128(r0, y0), _mm_setr_epi16(-vs, vs, -vs, vs, -vs, vs, -vs, vs)); 
    v0 = _mm_srai_epi32(v0, 15); 
    __m128i v1 = _mm_madd_epi16(_mm_or_si128(r1, y1), _mm_setr_epi16(-vs, vs, -vs, vs, -vs, vs, -vs, vs)); 
    v1 = _mm_srai_epi32(v1, 15); 
    // pack to sbytes 
    __m128i v = _mm_packs_epi32(v0, v1); 
    v = _mm_packs_epi16(v, v); 

    // interleave :(
    // y0 u0 v0 y1 u1 v1 y2 u2 v2 y3 u3 v3 y4 u4 v4 y5 
    // u5 v5 y6 u6 v6 y7 u7 v7 
    __m128i shuf0 = _mm_setr_epi8(0, _, _, 1, _, _, 2, _, _, 3, _, _, 4, _, _, 5); 
    p0 = _mm_or_si128(_mm_or_si128(
     _mm_shuffle_epi8(y, shuf0), 
     _mm_shuffle_epi8(u, _mm_bslli_si128(shuf0, 1))), 
     _mm_shuffle_epi8(v, _mm_bslli_si128(shuf0, 2))); 
    __m128i shuf1 = _mm_setr_epi8(_, 5, _, _, 6, _, _, 7, _, _, _, _, _, _, _, _); 
    p1 = _mm_or_si128(_mm_or_si128(
     _mm_shuffle_epi8(y, _mm_srli_si128(shuf1, 2)), 
     _mm_shuffle_epi8(u, _mm_srli_si128(shuf1, 1))), 
     _mm_shuffle_epi8(v, shuf1)); 

    _mm_storeu_si128((__m128i*)(pixels + i), p0); 
    _mm_storel_epi64((__m128i*)(pixels + i + 16), p1); 
    } 
} 

이것은별로 좋지 않습니다. 모든 셔플과 (포장되지 않은) 팩으로는 너무 무겁습니다. 아이비 때문에 그런 문제는 아니야. 적은 인터리브 출력 형식을 사용하면 많은 도움이되어 셔플을 6 개 절약 할 수 있습니다. 물론 이것이 고정 점이기 때문에 이것을 AVX로 변환 할 수는 없지만 AVX2를 시도 할 수는 있습니다.

여기에는 pmaddwd 대신 pmulhrsw을 기반으로 한 "보다 수직적 인"버전이 있으며 테스트를 거치지 않았습니다 (아마 고장 났을 것입니다). asm이 더 좋아 보이지만 성능을 테스트하지 않았습니다. 상점에 병목 게다가

void toYUV(uint8_t* pixels, int size) 
{ 
    // r g b r g b r g b r g b r g b r | g b r g b r g b 
    char _ = -1; 
    __m128i rshuf0 = _mm_setr_epi8(0, _, 3, _, 6, _, 9, _, 12, _, 15, _, _, _, _, _); 
    __m128i rshuf1 = _mm_setr_epi8(_, _, _, _, _, _, _, _, _, _, _, _, 2, _, 5, _); 
    __m128i gshuf0 = _mm_setr_epi8(1, _, 4, _, 7, _, 10, _, 13, _, _, _, _, _, _, _); 
    __m128i gshuf1 = _mm_setr_epi8(_, _, _, _, _, _, _, _, _, _, 0, _, 3, _, 6, _); 
    __m128i bshuf0 = _mm_setr_epi8(2, _, 5, _, 8, _, 11, _, 14, _, _, _, _, _, _, _); 
    __m128i bshuf1 = _mm_setr_epi8(_, _, _, _, _, _, _, _, _, _, 1, _, 2, _, 5, _); 

    __m128i ryweight = _mm_set1_epi16(9798); // (short)round(0.299 * 2^15) 
    __m128i gyweight = _mm_set1_epi16(19235);// (short)round(0.587 * 2^15) 
    __m128i byweight = _mm_set1_epi16(3736); // (short)round(0.114 * 2^15) 
    __m128i uscale = _mm_set1_epi16(18492); // (short)floor(2^15/(2^15 - byweight) * 0.5 * 2^15) 
    __m128i uofs = _mm_set1_epi16(128); 
    __m128i vscale = _mm_set1_epi16(23372); // (short)floor(2^15/(2^15 - ryweight) * 0.5 * 2^15) 
    __m128i vofs = _mm_set1_epi16(128); 

    __m128i yshuf0 = _mm_setr_epi8(0, _, _, 2, _, _, 4, _, _, 6, _, _, 8, _, _, _); 
    __m128i yshuf1 = _mm_setr_epi8(10, _, _, 12, _, _, 14, _, _, _, _, _, _, _, _, _); 
    __m128i ushuf0 = _mm_setr_epi8(_, 0, _, _, 1, _, _, 2, _, _, 3, _, _, 4, _, _); 
    __m128i ushuf1 = _mm_setr_epi8(5, _, _, 6, _, _, 7, _, _, _, _, _, _, _, _, _); 

    for (int i = 0; i < size; i += 24) 
    { 
    __m128i p0 = _mm_loadu_si128((__m128i*)(pixels + i)); 
    __m128i p1 = _mm_loadl_epi64((__m128i*)(pixels + i + 16)); 

    __m128i r = _mm_or_si128(_mm_shuffle_epi8(p0, rshuf0), _mm_shuffle_epi8(p1, rshuf1)); 
    __m128i g = _mm_or_si128(_mm_shuffle_epi8(p0, gshuf0), _mm_shuffle_epi8(p1, gshuf1)); 
    __m128i b = _mm_or_si128(_mm_shuffle_epi8(p0, bshuf0), _mm_shuffle_epi8(p1, bshuf1)); 

    __m128i scaledr = _mm_mulhrs_epi16(r, ryweight); 
    __m128i scaledg = _mm_mulhrs_epi16(g, gyweight); 
    __m128i scaledb = _mm_mulhrs_epi16(b, byweight); 
    __m128i y = _mm_add_epi16(_mm_add_epi16(scaledr, scaledg), scaledb); 
    __m128i u = _mm_mulhrs_epi16(_mm_sub_epi16(b, y), uscale); 
    __m128i v = _mm_mulhrs_epi16(_mm_sub_epi16(r, y), vscale); 
    // pack back to bytes and shuffle 
    // words in y are guaranteed to be in [0 - 255] so just shuffle them into place 
    // swords in u and v may be slightly wonky so pack with saturation first 
    u = _mm_packs_epi16(u, u); 
    v = _mm_packs_epi16(v, v); 
    p0 = _mm_or_si128(_mm_or_si128(
     _mm_shuffle_epi8(y, yshuf0), 
     _mm_shuffle_epi8(u, ushuf0)), 
     _mm_shuffle_epi8(v, _mm_bslli_si128(ushuf0, 1))); 
    p1 = _mm_or_si128(_mm_or_si128(
     _mm_shuffle_epi8(y, yshuf1), 
     _mm_shuffle_epi8(u, ushuf1)), 
     _mm_shuffle_epi8(v, _mm_bslli_si128(ushuf1, 1))); 

    _mm_storeu_si128((__m128i*)(pixels + i), p0); 
    _mm_storel_epi64((__m128i*)(pixels + i + 16), p1); 
    } 
}