2012-06-07 11 views
11

인텔 i3 프로세서에서 32 개 요소 (각 1 바이트 데이터)의 합계를 줄이려고합니다. 나는 이것을했다 :인텔에서 SSE2를 사용하여 오버플로없이 부호없는 바이트의 합계를 줄입니다.

s=0; 
for (i=0; i<32; i++) 
{ 
    s = s + a[i]; 
} 

그러나 내 응용 프로그램이 훨씬 적은 시간을 필요로하는 실시간 응용 프로그램이기 때문에 더 많은 시간이 걸린다. 최종 합계가 255를 초과 할 수 있습니다.

저수준 SIMD SSE2 명령어를 사용하여 구현할 수있는 방법이 있습니까? 불행히도 나는 SSE를 사용한 적이 없다. 이 목적을 위해 sse2 함수를 검색해 보았지만 사용할 수 없습니다. 작은 크기의 문제에 대한 계산 시간을 줄이는 것이 보장됩니까?

제안 사항

참고 : OpenCL 및 CUDA를 사용하여 비슷한 알고리즘을 구현했으며 문제 크기가 클 경우에만 효과가있었습니다. 작은 크기의 문제의 경우 오버 헤드 비용이 더 많이 들었습니다. SSE에서 작동하는 방법을 잘 모르십시오. PSADBW을 악용하여 작은 수평 총계를 빠르게 계산할 수 있습니다.

+0

합계가 255보다 큽니까? – hirschhornsalz

+0

예 최종 합계가 255보다 클 수 있습니다. – gpuguy

답변

7

이 같은

뭔가 (하지 테스트)

pxor xmm0, xmm0 
psadbw xmm0, [a + 0] 
pxor xmm1, xmm1 
psadbw xmm1, [a + 16] 
paddw xmm0, xmm1 
pshufd xmm1, xmm0, 2 
paddw xmm0, xmm1 ; low word in xmm0 is the total sum 

시도 내장 버전 :이 코드는 아마 전혀 이해되지 않는다, 그래서

나는 내장 함수를 사용하지 않습니다. 해체는 괜찮아 보였다.

uint16_t sum_32(const uint8_t a[32]) 
{ 
    __m128i zero = _mm_xor_si128(zero, zero); 
    __m128i sum0 = _mm_sad_epu8(
         zero, 
         _mm_load_si128(reinterpret_cast<const __m128i*>(a))); 
    __m128i sum1 = _mm_sad_epu8(
         zero, 
         _mm_load_si128(reinterpret_cast<const __m128i*>(&a[16]))); 
    __m128i sum2 = _mm_add_epi16(sum0, sum1); 
    __m128i totalsum = _mm_add_epi16(sum2, _mm_shuffle_epi32(sum2, 2)); 
    return totalsum.m128i_u16[0]; 
} 
+0

위의 경우 인텔 ® C++ 컴파일러 내장 함수를 작성할 수 있습니까? – gpuguy

+0

@ gpuguy 시도했지만, 어쩌면 뭔가를 엉망으로 만들려고 intrinsics를 사용하지 않았습니다. 그 'reinterpret_cast' 너무 좋아 보이지 않는데, 나는 그것을 없애하는 방법을 알아낼 수 없습니다. – harold

+0

'int8_t' ('uint8_t' 대신)에서 이와 똑같은 트릭을 사용하려면 : range shift를 unsigned (xor with 0x80)로 옮긴 다음 합계에서'16 * 0x80'을 뺍니다. Intrinsics가있는 예제는 Agner Fog의 벡터 클래스 라이브러리에 대한 패치입니다 (https://github.com/pcordes/vectorclass/commit/630ca802bb1abefd096907f8457d090c28c8327b). 같은 생각이 [AVX2 ymm 벡터] (https://github.com/pcordes/vectorclass/commit/11aa77071d25d7d93090789006250f8992f44272))에서 작동합니다. –

5

이 조금 긴 호흡하지만 여전히 스칼라 코드보다 적어도 2 배 더 빠른 같아야 a[] 요구 정렬 16 바이트가있을 것을

uint16_t sum_32(const uint8_t a[32]) 
{ 
    const __m128i vk0 = _mm_set1_epi8(0); // constant vector of all 0s for use with _mm_unpacklo_epi8/_mm_unpackhi_epi8 
    __m128i v = _mm_load_si128(a);   // load first vector of 8 bit values 
    __m128i vl = _mm_unpacklo_epi8(v, vk0); // unpack to two vectors of 16 bit values 
    __m128i vh = _mm_unpackhi_epi8(v, vk0); 
    __m128i vsum = _mm_add_epi16(vl, vh); 
    v = _mm_load_si128(&a[16]);    // load second vector of 8 bit values 
    vl = _mm_unpacklo_epi8(v, vk0);   // unpack to two vectors of 16 bit values 
    vh = _mm_unpackhi_epi8(v, vk0); 
    vsum = _mm_add_epi16(vsum, vl); 
    vsum = _mm_add_epi16(vsum, vh); 
    // horizontal sum 
    vsum = _mm_add_epi16(vsum, _mm_srli_si128(vsum, 8)); 
    vsum = _mm_add_epi16(vsum, _mm_srli_si128(vsum, 4)); 
    vsum = _mm_add_epi16(vsum, _mm_srli_si128(vsum, 2)); 
    return _mm_extract_epi16(vsum, 0); 
} 

참고.

_mm_hadd_epi16을 사용하면 위의 코드를 향상시킬 수 있습니다.

+0

[]가 16 바이트로 정렬되어 있는지 어떻게 확인합니까? SSE에서는 CUDA에서 __align __ (16)과 유사한 것이 있습니까? – gpuguy

+0

그것은 사용하는 컴파일러와 OS에 따라 다릅니다. 동적이 아닌 할당을 사용하는 gcc는'__attribute__ ((aligned (16)))'을 사용합니다 - 리눅스에서의 동적 인 할당은'memalign()'또는'posix_memalign()'을 사용합니다. –

+0

이걸 downvote해야 할거야; 그것은 작동하지만'psadbw'는 올바른 답입니다. 서명 된'int8_t'에 대해서는'xor'로 부호없는 범위로 이동하여 각 바이트에 0x80을 더할 수 있으며 결과에서'16 * 0x80'을 뺍니다. ([Agner Fog의 벡터 클래스 라이브러리를위한이 패치] (https : // github.co.kr/pcordes/vectorclass/commit/630ca802bb1abefd096907f8457d090c28c8327b) (예 : 내장 함수 포함). 같은 생각이 [AVX2 ymm 벡터] (https://github.com/pcordes/vectorclass/commit/11aa77071d25d7d93090789006250f8992f44272))에서 작동합니다. 하지만 여기서 OP는 이미 서명되지 않은 것 같습니다. 그래서 psadbw는 큰 승리입니다. –