2017-03-10 8 views
3

나는 최근에 내 프로그램은 다음과 같은 간단한 기능에 대부분의 시간을 보내는 것으로 나타났습니다 :SSE 최적화

void SumOfSquaredDifference(
    const uint8_t * a, size_t aStride, const uint8_t * b, size_t bStride, 
    size_t width, size_t height, uint64_t * sum) 
{ 
    *sum = 0; 
    for(size_t row = 0; row < height; ++row) 
    { 
     int rowSum = 0; 
     for(size_t col = 0; col < width; ++col) 
     { 
      int d = a[col] - b[col]; 
      rowSum += d*d; 
     } 
     *sum += rowSum; 
     a += aStride; 
     b += bStride; 
    } 
} 

이 기능은 2 개의 8 비트 그레이 이미지의 제곱 차이의 합을 찾습니다. SSE를 사용하여 성능을 향상시키는 방법이 있다고 생각하지만이 분야에 대한 경험이 없습니다. 아무도 도와 줄 수 있습니까?

+3

수동으로 최적화를 시작하기 전에 컴파일러가 아직 벡터화 된 코드를 생성하고 있지 않은지 확인해야합니다. 최적화 및 SIMD가 활성화되어 있는지 확인하고 생성 된 코드에서 SSE 지침을 확인하십시오. 그렇지 않으면 바보 같은 일에 시간을 낭비 할 수 있습니다. (BTW, 사용중인 CPU 하드웨어, OS 및 컴파일러를 지정하십시오.) –

+0

외부 루프가 쓸모없는 것처럼 보입니다. 그게 실수 야? –

+1

@ A.S.H : 이상하게 보입니다. 그러나 그는 각 행 반복에서'a','b' 포인터를 부딪 히고 있습니다. –

답변

7

물론 코드를 개선 할 수 있습니다. 이 SSE2를 사용하여 기능의 최적화의 예 :

const __m128i Z = _mm_setzero_si128(); 
const size_t A = sizeof(__m128i); 

inline __m128i SquaredDifference(__m128i a, __m128i b) 
{ 
    const __m128i aLo = _mm_unpacklo_epi8(a, Z); 
    const __m128i bLo = _mm_unpacklo_epi8(b, Z); 
    const __m128i dLo = _mm_sub_epi16(aLo, bLo); 

    const __m128i aHi = _mm_unpackhi_epi8(a, Z); 
    const __m128i bHi = _mm_unpackhi_epi8(b, Z); 
    const __m128i dHi = _mm_sub_epi16(aHi, bHi); 

    return _mm_add_epi32(_mm_madd_epi16(dLo, dLo), _mm_madd_epi16(dHi, dHi)); 
} 

inline __m128i HorizontalSum32(__m128i a) 
{ 
    return _mm_add_epi64(_mm_unpacklo_epi32(a, Z), _mm_unpackhi_epi32(a, Z)); 
} 

inline uint64_t ExtractSum64(__m128i a) 
{ 
    uint64_t _a[2]; 
    _mm_storeu_si128((__m128i*)_a, a); 
    return _a[0] + _a[1]; 
} 

void SumOfSquaredDifference(
    const uint8_t *a, size_t aStride, const uint8_t *b, size_t bStride, 
    size_t width, size_t height, uint64_t * sum) 
{ 
    assert(width%A == 0 && width < 0x10000); 
    __m128i fullSum = Z; 
    for(size_t row = 0; row < height; ++row) 
    { 
     __m128i rowSum = Z; 
     for(size_t col = 0; col < width; col += A) 
     { 
      const __m128i a_ = _mm_loadu_si128((__m128i*)(a + col)); 
      const __m128i b_ = _mm_loadu_si128((__m128i*)(b + col)); 
      rowSum = _mm_add_epi32(rowSum, SquaredDifference(a_, b_)); 
     } 
     fullSum = _mm_add_epi64(fullSum, HorizontalSum32(rowSum)); 
     a += aStride; 
     b += bStride; 
    } 
    *sum = ExtractSum64(fullSum); 
} 

이 예에서는 단순화 몇 가지 (이미지 너비가 16의 배수가 아닌 경우 작동하지 않습니다)입니다. 알고리즘의 전체 버전은 here입니다.

그리고 SSSE3 버전에서 몇 가지 마법 :

const __m128i K_1FF = _mm_set1_epi16(0x1FF); 

inline __m128i SquaredDifference(__m128i a, __m128i b) 
{ 
    const __m128i lo = _mm_maddubs_epi16(_mm_unpacklo_epi8(a, b), K_1FF); 
    const __m128i hi = _mm_maddubs_epi16(_mm_unpackhi_epi8(a, b), K_1FF); 
    return _mm_add_epi32(_mm_madd_epi16(lo, lo), _mm_madd_epi16(hi, hi)); 
} 

마법 설명 (_mm_maddubs_epi16 참조) GCC 코드를 벡터화를 장려 스위치가

K_1FF -> {-1, 1, -1, 1, ...}; 
_mm_unpacklo_epi8(a, b) -> {a0, b0, a1, b1, ...}; 
_mm_maddubs_epi16(_mm_unpacklo_epi8(a, b), K_1FF) -> {b0 - a0, b1 - a1, ...}; 
+1

SSSE3 버전은 어떻게 진행되고 있습니까? – harold

+0

도서관 링크를 제공해 주셔서 감사합니다! 그것은 굉장! –

+0

내가 찾던 곳과 가깝습니다. – Carlos

0

. 예를 들어, -mfma 스위치는 두 배를 사용하여 이와 같은 단순 루프에서 약 25 %의 속도 증가를 제공합니다. 8 비트 정수를 사용하는 것이 더 낫다고 상상해보십시오. 코드가 읽기 쉽기 때문에 직접 작성한 최적화보다 더 선호합니다. 말했다

, 당신의 루프 속도를 높일 수있는 몇 가지 오래 된 트릭이 있습니다 :

  • 은하지 인덱스를 수행마다 루프 반복에서 포인터를 증가. 바깥 쪽 루프에서 이렇게하면 안쪽 루프에서도 똑같이해야합니다. 내부 루프에 들어가기 전에 새 포인터를 만들 수 있으므로 +=stride이 유효합니다.

  • 루프 내부의 합계 포인터에 할당하지 말고 로컬 변수를 사용하여 누적하여 완료되면 출력으로 복사하십시오. rowSum을 사용하지만 내부 루프에서만 사용합니다. 대신 두 루프에서 해당 변수를 사용하십시오.