2012-01-24 7 views
8

원시 픽셀 데이터를 처리하는 일부 코드를 최적화하려고했습니다. 현재 코드의 C++ 구현이 너무 느리기 때문에 MSVC 2008에서 SSE 내장 함수 (SSE/2/3 사용하지 않고 4)를 사용하여 몇 가지 근거를 만들려고 노력했습니다. 처음으로이 최저치를 파고 들었을 때, 몇 가지 좋은 진전을 보였습니다.SSE 내장 함수 - 비교 if/else 최적화

불행하게도, 나는 내가 붙어있다 코드의 특정 부분에 왔어요 :

//Begin bad/suboptimal SSE code 
__m128i vnMask = _mm_set1_epi16(0x0001); 
__m128i vn1  = _mm_and_si128(vnFloors, vnMask); 

for(int m=0; m < PBS_SSE_PIXELS_PROCESS_AT_ONCE; m++) 
{ 
    bool bIsEvenFloor = vn1.m128i_u16[m]==0; 

    vnPxChroma.m128i_u16[m] = 
     m%2==0 
      ? 
     (bIsEvenFloor ? vnPxCeilChroma.m128i_u16[m] : vnPxFloorChroma.m128i_u16[m]) 
      : 
     (bIsEvenFloor ? vnPxFloorChroma.m128i_u16[m] : vnPxCeilChroma.m128i_u16[m]); 
} 

는 현재, 나는이 부분에 대한 C++ 구현를 사용하여 디폴트 이야 내가 할 수 있기 때문에 꽤 이것이 SSE를 사용하여 최적화 될 수있는 방법에 대해 머리를 써보십시오. - 비교를위한 SSE 내장 함수가 약간 까다로운 것으로 나타났습니다.

모든 제안/팁을 주시면 감사하겠습니다.

EDIT : 한 번에 하나의 픽셀을 처리 등가 C++ 코드가 될 것이다 : 16 : 3 :

short pxCl=0, pxFl=0; 
short uv=0; // chroma component of pixel 
short y=0; // luma component of pixel 

for(int i = 0; i < end-of-line, ++i) 
{ 
    //Initialize pxCl, and pxFL 
    //... 

    bool bIsEvenI  = (i%2)==0; 
    bool bIsEvenFloor = (m_pnDistancesFloor[i] % 2)==0; 

    uv = bIsEvenI ==0 
     ? 
    (bIsEvenFloor ? pxCl : pxFl) 
     : 
    (bIsEvenFloor ? pxFl : pxCl); 

    //Merge the Y/UV of the pixel; 
    //... 
} 

기본적으로, I 4에서 비선형 에지 스트레칭 일 오전 9.

+2

SSE의 내장 함수는 읽기 어려운 : 그때부터, 나는 솔루션을 최적화하기 위해 SSE 벡터화를 사용할 수 있었다. 이 섹션을 설명하기 위해 몇 가지 의견/동등한 C++ 코드 블록을 추가 하시겠습니까? –

+0

코드에서 수행하고자하는 작업은 무엇입니까? – ronag

+0

나는이 스 니펫 (암호없는 식별자 및 컨텍스트 없음)에 다소 당혹 스럽지만 비교를 곱셈 및 덧셈으로 바꾸지 않는 이유는 무엇입니까? – zrxq

답변

7

좋아, 그래서이 코드가 무슨 일을하는지 모르겠다.하지만 ternery 연산자를 최적화하고 SSE에서만 작동하는이 코드 부분을 얻는 방법을 묻는 것을 알고있다. 첫 번째 단계로 조건부 연산자를 피하기 위해 정수 플래그와 곱셈을 사용하는 접근 방식을 시도하는 것이 좋습니다. 예를 들어 :

이 섹션

for(int m=0; m < PBS_SSE_PIXELS_PROCESS_AT_ONCE; m++) 
{ 
    bool bIsEvenFloor = vn1.m128i_u16[m]==0;  

    vnPxChroma.m128i_u16[m] = m%2==0 ? 
     (bIsEvenFloor ? vnPxCeilChroma.m128i_u16[m] : vnPxFloorChroma.m128i_u16[m]) : 
     (bIsEvenFloor ? vnPxFloorChroma.m128i_u16[m] : vnPxCeilChroma.m128i_u16[m]); 
} 

는 시리얼 메모리 액세스의 성능 향상을 잃게되지만 모듈로 연산과 두 개의 드롭 두 개의 루프로 분할하여 기본적으로이

// DISCLAIMER: Untested both in compilation and execution 

// Process all m%2=0 in steps of 2 
for(int m=0; m < PBS_SSE_PIXELS_PROCESS_AT_ONCE; m+=2) 
{ 
    // This line could surely pack muliple u16s into one SSE2 register 
    uint16 iIsOddFloor = vn1.m128i_u16[m] & 0x1 // If u16[m] == 0, result is 0 
    uint16 iIsEvenFloor = iIsOddFloor^0x1 // Flip 1 to 0, 0 to 1 

    // This line could surely perform an SSE2 multiply across multiple registers 
    vnPxChroma.m128i_u16[m] = iIsEvenFloor * vnPxCeilChroma.m128i_u16[m] + 
           iIsOddFloor * vnPxFloorChroma.m128i_u16[m] 
} 

// Process all m%2!=0 in steps of 2 
for(int m=1; m < PBS_SSE_PIXELS_PROCESS_AT_ONCE; m+=2) 
{ 
    uint16 iIsOddFloor = vn1.m128i_u16[m] & 0x1 // If u16[m] == 0, result is 0 
    uint16 iIsEvenFloor = iIsOddFloor^0x1 // Flip 1 to 0, 0 to 1 

    vnPxChroma.m128i_u16[m] = iIsEvenFloor * vnPxFloorChroma.m128i_u16[m] + 
           iIsOddFloor * vnPxCeilChroma.m128i_u16[m] 
} 

문법적으로 동일합니다 조건부 연산자.

이제 루프 당 두 개의 부울 연산자와 추가 할 수있는 곱셈 이 있음을 알게됩니다. 내 고유 구현 SSB는이 아닙니다. vn1.m123i_u16 [] 어레이에 저장된 내용은 무엇입니까? 단지 0과 1이 맞습니까? 그렇다면이 부분이 필요 없으므로이 부분을 없앨 수 있습니다. 그렇지 않다면이 배열의 데이터를 정규화하여 0과 1 만 가질 수 있습니까? vn1.m123i_u16 배열은 다음과 0이 포함되어있는 경우이 코드는 또한 내가 SSE는 isEvenFloor * vnPx... part을 수행 할 수없고 iIsEvenFlooriIsOddFloor 레지스터를 저장하는 곱 사용하지 않는 알 수

uint16 iIsOddFloor = vn1.m128i_u16[m] 
uint16 iIsEvenFloor = iIsOddFloor^0x1 // Flip 1 to 0, 0 to 1 

된다. 죄송합니다. u16에 대한 SSE 내장 함수를 기억할 수 없지만,이 방법이 도움이되기를 바랍니다. 당신이 게시 한, 내 수정, 우리는 여전히 SSE1/2/3 내장 함수의 전체를 사용을하지 않는 코드의이 섹션에서는

// This line could surely pack muliple u16s into one SSE2 register 
uint16 iIsOddFloor = vn1.m128i_u16[m] & 0x1 // If u16[m] == 0, result is 0 
uint16 iIsEvenFloor = iIsOddFloor^0x1 // Flip 1 to 0, 0 to 1 

// This line could surely perform an SSE2 multiply across multiple registers 
vnPxChroma.m128i_u16[m] = iIsEvenFloor * vnPxCeilChroma.m128i_u16[m] + 
          iIsOddFloor * vnPxFloorChroma.m128i_u16[m] 

하지만 몇 가지 포인트를 제공 할 수 있습니다 : 일부 최적화 당신은에 보일 것입니다 (코드를 벡터화하는 방법).

마지막으로 나는 모든 것을 테스트한다고 말할 것입니다. 변경 및 프로파일 링을 다시하기 전에 위의 코드를 변경하지 않고 실행하십시오.실제 성능 수치는 당신을 놀라게 할 수 있습니다!


업데이트 1 : 나는 Intel SIMD Intrinsics documentation 겪은

이에 사용 될 수있는 관련 내장 함수를 선택합니다. 구체적으로는 비트 단위 XOR을 살펴보고, 및 __m128i 데이터 형식 여섯 8 비트, 8-16 비트, 네 개의 32- 비트 또는 2 (64)를 수납 할 수 MULT는/

__m128 데이터 형식
담기 비트 정수 값.

__m128i _mm_add_epi16 (__ m128i의 A, __m128i B는)
가 B의 부호 8 또는 부호없는 16 비트 정수로 A의 8 개 부호 또는 부호없는 16 개 비트 정수 추가

__m128i _mm_mulhi_epu16 (__ m128i , __m128i b)
a의 8 개의 부호없는 16 비트 정수에 b의 8- 부호없는 16 비트 정수를 곱합니다. 는 상위 8 부호없는 32 비트 16 비트

R0 = HIWORD (A0의 *의 B0)
R1 = HIWORD (A1에 *를 B1)
R2 = HIWORD (A2의 *의 B2)
결과 팩 R3 = HIWORD (A3의 *의 B3)
..
R7 = HIWORD (A7의 *의 B7)

__m128i _mm_mullo_epi16 (__ m128i의 A, __m128i의 b)
곱 8 부호 또는 부호없는 16 비트 정수에서 a는 8-signed 또는 unsigned 16- b의 정수입니다. 는 상위 16 비트 팩 8 부호 또는 부호없는 32 비트 R0 = LOWORD (A0의 *의 B0)
R1 = LOWORD (A1에 *를 B1)
R2 = LOWORD (A2의 *의 B2)

결과
R3 = LOWORD (A3의 *의 B3)
..
R7 = LOWORD (A7의 *의 B7)

__m128i _mm_and_si128 (__ m128i의 A, __m128i의 b)
은 비트 단위로 수행하고 128 비트의 m1 단위의 값과 m2 단위의 128 비트 값

__m128i _mm_andnot_si128 (__ m128i A는, __m128i B는)
이 비트를 계산하고, (B)의 128 비트 값 NOT A의 128 비트 값의 논리합.

__m128i _mm_xor_si128 (__ m128i의 A, B의 __m128i)
가 m2에서 128 비트 값 M1 (128)의 비트 값의 비트 단위 XOR을 수행한다. 참조

에 대한 코드 예제에서도

UINT16 U1 = U2 = U3 ...= u15 = 0x1
__m128i vnMask = _mm_set1_epi16 (0x0001); // 8 개의 부호있는 16 비트 정수 값을 설정합니다.

UINT16의 VN1 [I] = vnFloors의 [I] &을 0x1
__m128i VN1 = _mm_and_si128 (vnFloors, vnMask); // a의 128 비트 값과 b의 128 비트 값의 비트 단위 AND를 계산합니다.

+0

곱셈 대신 비트 단위 AND를 사용할 수 있습니까? – zrxq

+0

감사합니다. C++ 구현을 이미 두 개의 루프 용으로 나누었습니다. 비교를 위해 곱셈/덧셈을 사용하지 않았습니다. 내가 원하는 부분은 두 개의 루프를 하나의 명령어 세트로 결합하는 것입니다. – ZeroDefect

+0

@zrxd 예. 나는 그것을 깨달았다. 편집했다. 보세요. –

2

Andrew 귀하의 제안에 따라 최적의 솔루션이 나옵니다. 진리표와 카르노 맵의 조합을 사용

, 나는 코드

uv = bIsEvenI ==0 
    ? 
(bIsEvenFloor ? pxCl : pxFl) 
    : 
(bIsEvenFloor ? pxFl : pxCl); 

가! XOR (안 XOR) 기능이있는 아래로 삶은 것을 발견했다. 모든 도움

//Use the mask with bit AND to check if even/odd 
__m128i vnMask    = _mm_set1_epi16(0x0001); 

//Set the bit to '1' if EVEN, else '0' 
__m128i vnFloorsEven  = _mm_andnot_si128(vnFloors, vnMask); 
__m128i vnMEven    = _mm_set_epi16 
    (
     0, //m==7 
     1, 
     0, 
     1, 
     0, 
     1, 
     0, //m==1 
     1 //m==0 
    ); 


// Bit XOR the 'floor' values and 'm' 
__m128i vnFloorsXorM  = _mm_xor_si128(vnFloorsEven, vnMEven); 

// Now perform our bit NOT 
__m128i vnNotFloorsXorM  = _mm_andnot_si128(vnFloorsXorM, vnMask); 

// This is the C++ ternary replacement - using multipilaction 
__m128i vnA     = _mm_mullo_epi16(vnNotFloorsXorM, vnPxFloorChroma); 
__m128i vnB     = _mm_mullo_epi16(vnFloorsXorM, vnPxCeilChroma); 

// Set our pixels - voila! 
vnPxChroma     = _mm_add_epi16(vnA, vnB); 

감사합니다 ...

+0

와우! 해결책을 게시하는 일을 잘하고 잘했습니다! SSE 버전과 바닐라 C++ 코드와 같은 성능은 무엇입니까? '진리표와 카르노 맵'그것처럼. 나는 GCSE 전자 공학을 위해 그 일을 기억한다! –

+1

감사합니다. SSE 구현은 절반도되지 않아 실행됩니다. 원래 (이 포스트를 게시하기 전에), 나는 그것이 제안을 공유하기를 희망하면서 어셈블 된 C++ 구현물을 살펴 보았다. 불행히도 C++ 구현은 모든 분기 (및 캐시 누락)로 인해 심각하게 방해받습니다.! xor 패턴을 최대한 활용하지 못합니다. 그리고 네, 카르노 맵은 폭탄입니다. – ZeroDefect

+0

좋아요! u16을 사용하고 있지만 이론적 인 속도 개선 속도는 최대 8 배입니다. 아마 당신이 알아 낸 것보다 더 쉽게 말한 것입니다! –