2014-10-29 3 views
2

두 개의 SSE 레지스터가 모두 0이 아닌지 테스트하고 싶습니다.두 개의 SSE 레지스터가 모두 0이 아닌지 확인하기

uint8_t *src; // Assume it is initialized and 16-byte aligned 
__m128i xmm0, xmm1, xmm2; 

xmm0 = _mm_load_si128((__m128i const*)&src[i]); // Need to preserve xmm0 & xmm1 
xmm1 = _mm_load_si128((__m128i const*)&src[i+16]); 
xmm2 = _mm_or_si128(xmm0, xmm1); 
if (!_mm_testz_si128(xmm2, xmm2)) { // Test both are not zero 
} 

이것이 가장 좋은 방법 (SSE 4.2까지 사용)인가 :

내가 현재 가지고있는 코드?

+0

더 나은 무엇을 희망 할 수 있습니까? OR을 최대한 활용하십시오. –

+3

'_mm_testz_si128' 대신'_mm_movemask_epi8'을 사용했지만 실제로는 _mm_testz_si128'이 일반적으로 더 좋습니다. '_mm_movemask_epi8'는 Nahalem과 Westmere에서만 지연 시간이 짧습니다. 그러나 Haswell에 더 나쁘다. 하지만 더 중요한 것은 FLAGS 레지스터에 0이나 carry 플래그를 설정하지 않지만'_mm_testz_si128'는 플래그를 설정한다는 것입니다. 이제 네가 가진 것이 아마도 최고 일 것이다. –

+0

사실 이것은 내가 찾고있는 토론 유형이었습니다. 나는 그것을 답으로 표시 하겠지만 그것은 논평이다. – ChipK

답변

3

나는이 질문에서 유용한 것을 배웠다. 일부 스칼라 코드

extern foo2(int x, int y); 
void foo(int x, int y) { 
    if((x || y)!=0) foo2(x,y); 
} 

에서하자 첫 모습이 gcc -O3 -S -masm=intel test.c 같은이를 컴파일하고 중요한 어셈블리는

mov  eax, edi ; edi = x, esi = y -> copy x into eax 
or  eax, esi ; eax = x | y and set zero flag in FLAGS if zero 
jne  .L4  ; jump not zero 
이제

의이 SIMD 제로를 등록 테스트를 살펴 보자입니다. 스칼라 코드와 달리 SIMD FLAGS 레지스터는 없다. 그러나 SSE4.1에는 스칼라 FLAGS 레지스터에 제로 플래그 (및 캐리 플래그)를 설정할 수있는 SIMD 테스트 명령어가있다. c99 -msse4.1 -O3 -masm=intel -S test_SSE.c과 중요한 어셈블리

extern foo2(__m128i x, __m128i y); 
void foo(__m128i x, __m128i y) { 
    __m128i z = _mm_or_si128(x,y); 
    if (!_mm_testz_si128(z,z)) foo2(x,y); 
} 

컴파일이 한 번 더 명령을 소요

movdqa  xmm2, xmm0 ; xmm0 = x, xmm1 = y, copy x into xmm2 
por   xmm2, xmm1 ; xmm2 = x | y 
ptest  xmm2, xmm2 ; set zero flag if zero 
jne   .L4  ; jump not zero 

공지 사항 때문에 압축 된 비트 단위 OR 제로 플래그를 설정하지 않습니다. 스칼라 버전과 SIMD 버전 모두 추가 레지스터 (스칼라의 경우 eax, SIMD의 경우 xmm2)를 사용해야합니다. 질문에 대한 답변을 얻으려면 현재 해결 방법이 최선입니다.

그러나 SSE4.1 이상 프로세서가없는 경우 _mm_movemask_epi8을 사용해야합니다. 단지 SSE2를 필요로 또 다른 대안은 중요한 조립이가 SSE4.1 ptest 명령으로 다음 한 번 더 교육을 필요로하는

movdqa  xmm2, xmm0 
por   xmm2, xmm1 
pmovmskb eax, xmm2 
test  eax, eax 
jne   .L4 

공지 사항입니다 _mm_movemask_epi8

extern foo2(__m128i x, __m128i y); 
void foo(__m128i x, __m128i y) { 
    if (_mm_movemask_epi8(_mm_or_si128(x,y))) foo2(x,y); 
} 

사용하는 것입니다.

지금까지는 ptest보다 대기 시간이 더 우수하기 때문에 pmovmaskb 명령을 사용했습니다. 그러나, Haswell 전에 이것을 깨달았습니다. Haswell에서 대기 시간이 pmovmaskb은 대기 시간이 ptest보다 나쁩니다. 둘 다 동일한 처리량을가집니다. 하지만이 경우에는별로 중요하지 않습니다. 중요한 것은 (전에는 깨닫지 못했지만) pmovmaskb은 FLAGS 레지스터를 설정하지 않았기 때문에 다른 명령이 필요하다는 것입니다. 이제 중요한 루프에서 ptest을 사용할 것입니다. 질문 해 주셔서 감사합니다.

편집 : OP에서 제안한 것처럼 다른 SSE 레지스터를 사용하지 않고이 작업을 수행 할 수있는 방법이 있습니다.

extern foo2(__m128i x, __m128i y); 
void foo(__m128i x, __m128i y) { 
    if (_mm_movemask_epi8(x) | _mm_movemask_epi8(y)) foo2(x,y);  
} 

GCC에서 관련 조립체는 :

pmovmskb eax, xmm0 
pmovmskb edx, xmm1 
or   edx, eax 
jne   .L4 

대신 다른 XMM이 두 스칼라 레지스터를 사용하는 레지스터 사용.

지침이 적을수록 반드시 더 나은 성능을 의미하지는 않습니다. 다음 중 가장 적합한 솔루션은 무엇입니까? 찾아 내기 위해 각자를 테스트해야합니다.

+0

if (_movemask_epi8 (x) | _movemask_epi8 (y))는 어떻습니까? 그것은 2 개의 movemskb와 하나의 '또는'명령을 생성하지 않을 것입니까? 총 3 개입니까? – ChipK

+0

@ChipK, 전적으로 맞습니다 (업데이트 된 답변 참조). 당신이 당신 자신의 질문에 대답 한 것처럼 보입니다. 다른 XMM 레지스터를 사용하지 않고도이 작업을 수행 할 수 있습니다. –

+2

이 대답의'pmovmskb' 부분은 가짜입니다. 당신은 단지 사인 비트를 테스트하고있다 : 결과는 어떤 바이트의 비트 0..6에도 의존하지 않는다! 'por' /'ptest' /'jnz'는 꽤 좋은 선택입니다. 다른 방법은 [uops와 동일한 수]입니다 (http://stackoverflow.com/questions/7989897/is-an-m128i-variable-zero/7991083#comment59454214_35890766). 'por' /'pcmpeqb' (모두 0) /'pmovmskb' /'test/jnz'. –

1

C/C++를 사용하는 경우 개별 CPU 레지스터를 제어 할 수 없습니다. 완전히 제어하려면 어셈블러를 사용해야합니다.

+1

맞아요, 컴파일러는 자신의 방식대로 레지스터를 할당 할 수 있습니다. –