2016-09-04 1 views
4

나는 특별한 유스 케이스를 염두에두고 있지 않다. 나는 이것이 인텔의 intrinsics에서의 설계상의 결함/한계인지 아니면 내가 뭔가를 놓치고 있는지 묻고있다.상위 요소를 초기화하는 명령을 낭비하는 컴파일러없이 스칼라를 벡터에 병합하는 방법은 무엇입니까? 인텔 내장 함수의 디자인 제한?

기존 벡터와 스칼라 플로트를 결합하려는 경우 인텔 고유 내장 함수를 사용하여 높은 요소를 0으로 설정하거나 벡터에 스칼라를 브로드 캐스팅하지 않는 방법이없는 것 같습니다. 나는 GNU C 기본 벡터 확장과 관련된 내장 함수를 조사하지 않았다.

여분의 내장 기능이 최적화되어 있지만 gcc (5.4 또는 6.2)를 사용하지 않는 경우 너무 좋지 않습니다. 관련된 내장 함수가 벡터 args만을 취하는 것과 관련하여 pmovzx 또는 insertps을로드하는 좋은 방법은 없습니다. (그리고 GCC는 ASM 명령에 scalar-> 벡터 적재 접하지 않는다.)

__m128 replace_lower_two_elements(__m128 v, float x) { 
    __m128 xv = _mm_set_ss(x);  // WANTED: something else for this step, some compilers actually compile this to a separate insn 
    return _mm_shuffle_ps(v, xv, 0); // lower 2 elements are both x, and the garbage is gone 
} 

GCC 5.3 -march = 할렘 -03 출력, 즉 인텔 CPU 용 SSE4.1하고 조정할 수 있도록 (그건 SSE4.1 없이는 더욱 악화되고, 상위 요소를 0으로 만드는 여러 명령어).

insertps xmm1, xmm1, 0xe # pointless zeroing of upper elements. shufps only reads the low element of xmm1 
    shufps xmm0, xmm1, 0  # The function *should* just compile to this. 
    ret 

TL은 : DR : 당신이 실제로 효율적으로이 작업을 수행 할 수있는 경우에이 질문의 나머지는 그냥 물어되고, 그렇지 않으면 왜 안.


그 소리의 셔플 최적화 프로그램은이 권리를 취득, 높은 요소 (_mm_set_ss(x)를) 영점 조정, 또는 그 (_mm_set1_ps(x))에 스칼라를 복제에 대한 지침을 낭비하지 않습니다. 컴파일러가 최적화해야하는 무언가를 작성하는 대신 C에서 "효율적으로"작성하는 방법이 있을까요? 최근의 gcc 조차도을 최적화하지 않기 때문에 실제 (그러나 사소한) 문제입니다. __m256 _mm256_castps128_ps256 (__m128 a)의 scalar-> 128B 당량이 있다면


이 가능하다. 즉, 상위 요소에 정의되지 않은 가비지가있는 __m128을 생성하고, 하위 요소의 부동 소수점을 생성하고, 스칼라 float/double이 이미 xmm 레지스터에있는 경우 asm 명령어를 0으로 컴파일합니다.

다음 내장 함수는 없지만이어야합니다.

  • 위에서 설명한대로 _mm256_castps128_ps256의 스칼라 -> __ m128 등가물입니다. 스칼라 - 이미 - 인 - 레지스터 경우에 대한 가장 일반적인 해결책.
  • __m128 _mm_move_ss_scalar (__m128 a, float s) : 벡터 a의 하위 요소를 스칼라 s으로 바꿉니다. 범용 스칼라 -> __ m128 (이전 글 머리 기호)이있는 경우 실제로는 필요하지 않습니다. (movss의 reg-reg 양식은 0 인로드 양식과 달리 병합되며 두 경우 모두 상위 요소를 0으로 만드는 movd과 다릅니다. 거짓 종속성이없는 스칼라 부동을 포함하는 레지스터를 복사하려면 movaps을 사용하십시오. 불편한 안전한 방법으로 인해 gcc가 최적화되지 않으므로
  • __m128i _mm_loadzxbd (const uint8_t *four_bytes) 및 기타 크기 PMOVZX/PMOVSX : AFAICT, there's no good safe way to use the PMOVZX intrinsics as a load이 있습니다.
  • __m128 _mm_insertload_ps (__m128 a, float *s, const int imm8).INSERTPS은로드와 다르게 동작합니다. imm8의 상위 2 비트는 무시되며 항상 메모리의 벡터에서 요소 대신 실효 주소에서 스칼라를 취합니다. 이렇게하면 매핑되지 않은 페이지 바로 앞에 float이있는 경우 16B로 정렬되지 않은 주소와 함께 작동하고 오류가 없어도 작동합니다.

    PMOVZX와 마찬가지로 gcc는 INSERT에 대한 메모리 피연산자로 _mm_load_ss() 상위 요소를 폴드하지 못합니다. (imm8의 상위 2 비트가 모두 0이 아니면 으로 _mm_insert_ps(xmm0, _mm_load_ss(), imm8)을 컴파일 할 수 있으며, src 요소가 실제로 MOVSS에 의해 메모리에서 생성 된 0 인 경우 다른 imm8이 vec에 포함됩니다. 실제로


모두 안전하다는 것을 그 중 하나를 에뮬레이트 할 수있는 실행 가능한 해결 방법이 있습니까)이 경우 XORPS/BLENDPS을 사용 (터치 수있는 그 예 로딩 (16B)에 의해 -O0에서 휴식하지 않습니다 다음 페이지와 segfault), 그리고 효율적인 (현재 gcc와 clang을 가진 -O3에서 낭비되는 명령어가 없으며, 다른 주요 컴파일러들도 선호된다)? 또한 가독성있는 방법으로도 가능하지만 필요하다면 __m128 float_to_vec(float a){ something(a); }과 같은 인라인 래퍼 함수 뒤에 넣을 수 있습니다.

인텔이 내장 함수를 도입하지 않은 이유가 있습니까? _mm256_castps128_ps256을 추가하는 것과 동시에 정의되지 않은 상위 ​​요소가있는 float -> __ m128을 추가 할 수있었습니다. 이것은 컴파일러 내부의 문제로 구현하기가 어렵습니까? 특히 ICC 내부? - 64에


주요 호출 규칙 (시스템 V 또는 MS __vectorcall)는 XMM0에 ARG 제 FP를 가지고 상부 정의 요소, FP XMM0 스칼라 인자들을 반환한다. ABI 문서의 경우 태그 위키를 참조하십시오. 즉, 컴파일러가 알 수없는 상위 요소가있는 레지스터에 스칼라 float/double을 갖는 것은 일반적이지 않습니다. 벡터화 된 내부 루프에서는이 작업이 거의 수행되지 않으므로 이러한 쓸모없는 지시를 피하는 것이 대부분 코드 크기를 약간 줄이는 것이라고 생각합니다.

pmovzx의 경우가 더 심각합니다. 내부 루프에서 사용할 수있는 경우입니다 (예 : VPERMD 셔플 마스크의 LUT의 경우, 캐시 풋 프린트에 4를 저장하는 대신 메모리에 32 비트를 채운 각 인덱스를 저장하는 경우).).


pmovzx로서의 부하 문제는 잠시 동안 지금 날 귀찮게하고 있으며, the original version of this question이 날은 XMM 레지스터에 스칼라 부동 소수점을 사용하는 관련 문제에 대해 생각하고 있어요. 아마도 pmovzx에 대한 유스 케이스가 스칼라 -> __ m128보다 많이 사용됩니다.

+1

:

한 예 :에 __m128 float_to_vec(float x){ return _mm_set_ss(x); }은 컴파일합니다. '_mm_load_ * '또는'_mm_set_ *'내장 함수를 사용할 때마다 정말 위조 된 코드를 생성합니다. 여기에있는 질문에 주어진 예제의 경우, 4 가지 이상의 명령 (!)을 얻을 수 있습니다 :'movaps xmm2, xmm1; xorps xmm3, xmm3; movss xmm3, xmm2; shufps xmm0, xmm3, 0'. 나는 기본적으로 그냥 포기했습니다. 기억에 넘치지 않는 어셈블리를 생성 할 수있는 한, 그것을 승리라고 부릅니다. –

답변

3

GNU C 인라인 asm으로 수행 할 수 있지만 이는 추한 것이므로 상수 전파 (https://gcc.gnu.org/wiki/DontUseInlineAsm)를 비롯한 많은 최적화가 실패합니다. 이것은 대답이이 아닙니다. 질문의 일부 대신 대답으로 이것을 추가하겠습니다. 따라서 이라는 질문은 짧게 유지됩니다.은 그리 크지 않습니다.

// don't use this: defeating optimizations is probably worse than an extra instruction 
#ifdef __GNUC__ 
__m128 float_to_vec_inlineasm(float x) { 
    __m128 retval; 
    asm ("" : "=x"(retval) : "0"(x)); // matching constraint: provide x in the same xmm reg as retval 
    return retval; 
} 
#endif 

이 원하는대로, 하나의 ret에 컴파일 않으며, 벡터로 당신에게 shufps 스칼라을 수 있도록 인라인됩니다

gcc5.3 
float_to_vec_and_shuffle_asm(float __vector(4), float): 
    shufps xmm0, xmm1, 0  # tmp93, xv, 
    ret 

Godbolt compiler explorer에이 코드를 참조하십시오.

이것은 분명히 원하지 않는 지침을 내 보내지 않도록 컴파일러와 싸울 필요가없는 순수한 어셈블리 언어에서 사소한 일입니다.


난 그냥 ret 명령어로 컴파일 __m128 float_to_vec(float a){ something(a); }를 작성하는 실제 방법을 발견하지 않았습니다. 을 시도한 결과, 실제로는 gcc 코드가 악화되었습니다 (위의 Godbolt 링크 참조). _mm_undefined_pd()_mm_move_sd()을 사용하면 코드가 더 나빠집니다. the existing float->__m128 intrinsics 아무도 도움이되지 않습니다.


오프 주제 : 실제 _mm_set_ss() 코드 세대 전략 : 당신이 상위 요소를 제로로이 코드를 작성 않으며, 컴파일러는 전략의 흥미로운 범위에서 선택. 어떤 좋은, 이상한. 위의 Godbolt 링크에서 볼 수 있듯이, 동일한 컴파일러 (gcc 또는 clang)에서 double과 float 사이의 전략도 다릅니다. 내가 MSVC 수많은 시간에이 밀접하게 관련 문제와 씨름 한

# gcc5.3 -march=core2 
    movd eax, xmm0  # movd xmm0,xmm0 would work; IDK why gcc doesn't do that 
    movd xmm0, eax 
    ret 

# gcc5.3 -march=nehalem 
    insertps  xmm0, xmm0, 0xe 
    ret 

# clang3.8 -march=nehalem 
    xorps xmm1, xmm1 
    blendps xmm0, xmm1, 14   # xmm0 = xmm0[0],xmm1[1,2,3] 
    ret 
+0

MSVC에서 r.f32 [0] = x;를 사용하고 clang에서'r [0] = x;'를 사용하면'asm ("": "x" (retval) : "0"(x));, 상수 폴딩을 잃지 않음. – plasmacel

+0

@plasmacel : 좋은 생각이지만, gcc는'_mm_set_ss'와 똑같은 것을 컴파일하고 ICC는 완전히 엉망입니다. https://godbolt.org/g/RC6CWb. gcc는 PXOR 또는 정수 XOR을 사용하여 잘못된 종속성을 깨는 것을 정말 좋아합니다. 초기화되지 않은 변수에 사용 된 레지스터를 제로로 만들고 나머지는 같은 * 레지스터로 초기화하는 것과 관련이 있는지 궁금합니다. –

+0

예, GCC 및 ICC의 경우 asm 버전으로 되돌아갑니다. 그런데 clang 3.9는 asm 버전을 컴파일하지도 않았고, 아마 컴파일러 버그와 관련이있다. – plasmacel

관련 문제