Есть интересный вопрос о семантике приведения C ++ (на который Microsoft уже кратко ответил вам), но он смешивается с неправильным использованием _mm_extract_ps
, что приводит к необходимости в первую очередь каламбура. (И только показывая asm, который эквивалентен, опуская преобразование int-> float.) Если кто-то хочет расширить стандартное в другом ответе, это было бы здорово.
TL: DR: используйте этовместо этого: это ноль или один shufps.Никаких извлечений, никаких типов ошибок.
template <int i> float get(__m128 input) {
__m128 tmp = input;
if (i) // constexpr i means this branch is compile-time-only
tmp = _mm_shuffle_ps(tmp,tmp,i); // shuffle it to the bottom.
return _mm_cvtss_f32(tmp);
}
Если у вас действительно есть сценарий использования памяти, вы должны искать в asm функцию, которая принимает float*
выходной аргумент, а не функцию, которая нуждается врезультат в xmm0
.(И да, это вариант использования для инструкции extractps
, но, возможно, не для встроенных _mm_extract_ps
. Gcc и clang используют extractps
при оптимизации *out = get<2>(in)
, хотя MSVC пропускает это и по-прежнему использует shufps + movss.)
Оба блока asm, которые вы показываете, просто копируют куда-то младшие 32 бита xmm0, без преобразования в int.Вы пропустили важные различия и показали только ту часть, которая просто бесполезно копирует битовый шаблон float
из xmm0, а затем обратно, двумя различными способами (для регистрации или в память).movd
- это чистая копия битов без изменений, точно так же, как и загрузка movss.
Выбор компилятора после того, как вы заставите его вообще использовать extractps
.Проход через регистр и обратно имеет меньшую задержку, чем сохранение / перезагрузка, но больше ALU выполняет.
Попытка (float const&)
type-pun действительно включает преобразование из FP в целое число, которое вы не делали.т шоу .Как если бы нам нужна была еще одна причина, чтобы избежать приведения указателя / ссылки для определения типа, это действительно означает что-то другое: (float const &) f принимает целочисленный битовый шаблон (из _mm_extract_ps
) какint
и преобразует это в float
.
Я поместил ваш код в проводник компилятора Godbolt , чтобы увидеть, что вы пропустили.
float get1_with_extractps_const(__m128 fmm) {
int f = _mm_extract_ps(fmm, 1);
return (float const&)f;
}
;; from MSVC -O2 -Gv (vectorcall passes __m128 in xmm0)
float get1_with_extractps_const(__m128) PROC ; get1_with_extractps_const, COMDAT
extractps eax, xmm0, 1 ; copy the bit-pattern to eax
movd xmm0, eax ; these 2 insns are an alternative to pxor xmm0,xmm0 + cvtsi2ss xmm0,eax to avoid false deps and zero the upper elements
cvtdq2ps xmm0, xmm0 ; packed conversion is 1 uop
ret 0
GCC компилирует это следующим образом:
get1_with_extractps_const(float __vector(4)): # gcc8.2 -O3 -msse4
extractps eax, xmm0, 1
pxor xmm0, xmm0 ; cvtsi2ss has an output dependency so gcc always does this
cvtsi2ss xmm0, eax ; MSVC's way is probably better for float.
ret
Очевидно, MSVC определяет поведение приведения указателя / ссылки для определения типа.Обычный ISO C ++ не (строгий псевдоним UB), как и другие компиляторы.Используйте memcpy
для ввода слов или объединения (которое GNU C и MSVC поддерживают в C ++ в качестве расширения).Конечно, в этом случае типирование элемента вектора, который вы хотите получить целым числом и обратно, ужасно.
Только для (float &)f
gcc предупреждает о нарушении строгого псевдонима. И GCC / clang согласны с MSVC, что только эта версия является каламбуром, не материализуя float
из неявного преобразования. C ++ странно!
float get1_with_extractps_nonconst(__m128 fmm) {
int f = _mm_extract_ps(fmm, 1);
return (float &)f;
}
<source>: In function 'float get_with_extractps_nonconst(__m128)':
<source>:21:21: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
return (float &)f;
^
gcc оптимизирует прочьextractps
в целом.
# gcc8.2 -O3 -msse4
get1_with_extractps_nonconst(float __vector(4)):
shufps xmm0, xmm0, 85 ; 0x55 = broadcast element 1 to all elements
ret
Clang использует SSE3 movshdup
для копирования элемента 1 в 0. (И элемента 3 в 2).Но MSVC этого не делает, что является еще одной причиной никогда не использовать это:
float get1_with_extractps_nonconst(__m128) PROC
extractps DWORD PTR f$[rsp], xmm0, 1 ; store
movss xmm0, DWORD PTR f$[rsp] ; reload
ret 0
Не используйте _mm_extract_ps
для этого
Обе ваши версииужасно, потому что это не то, что _mm_extract_ps
или extractps
для . Intel SSE: почему `_mm_extract_ps` возвращает` int` вместо `float`?
A float
в регистре - это то же самое, что и младший элемент вектора.Высокие элементы не должны быть обнулены.И если бы они это сделали, вы бы хотели использовать insertps
, который может делать элементы xmm, xmm и ноль в соответствии с немедленным.
Используйте _mm_shuffle_ps
, чтобы перевести нужный элемент в нижнее положение элемента.зарегистрируйтесь, и тогда это будет скалярным числом с плавающей точкой.(И вы можете сказать компилятору C ++, что с _mm_cvtss_f32
).Это должно компилироваться в shufps xmm0,xmm0,2
, без и extractps
или любым mov
.
template <int i> float get() const {
__m128 tmp = fmm;
if (i) // i=0 means the element is already in place
tmp = _mm_shuffle_ps(tmp,tmp,i); // else shuffle it to the bottom.
return _mm_cvtss_f32(tmp);
}
(я пропустил _MM_SHUFFLE(0,0,0,i)
, потому что это равно i
.)
Если бы ваша fmm
была в памяти, а не в регистре, то, надеюсь, компиляторы оптимизировали бы случайное перемешивание и просто movss xmm0, [mem]
.MSVC 19.14 удается это сделать, по крайней мере для аргумента function в случае стека.Я не тестировал другие компиляторы, но clang, вероятно, удастся оптимизировать _mm_shuffle_ps
;очень хорошо видеть сквозь тасования.
Тест-кейс, доказывающий, что это эффективно компилируется
Например, тест-кейс с версией вашей функции, не входящей в класс, и вызывающий, который вставляет еедля конкретного i
:
#include <immintrin.h>
template <int i> float get(__m128 input) {
__m128 tmp = input;
if (i) // i=0 means the element is already in place
tmp = _mm_shuffle_ps(tmp,tmp,i); // else shuffle it to the bottom.
return _mm_cvtss_f32(tmp);
}
// MSVC -Gv (vectorcall) passes arg in xmm0
// With plain dumb x64 fastcall, arg is on the stack, and it *does* just MOVSS load without shuffling
float get2(__m128 in) {
return get<2>(in);
}
Из проводника компилятора Godbolt , вывод asm из MSVC, clang и gcc:
;; MSVC -O2 -Gv
float get<2>(__m128) PROC ; get<2>, COMDAT
shufps xmm0, xmm0, 2
ret 0
float get<2>(__m128) ENDP ; get<2>
;; MSVC -O2 (without Gv, so the vector comes from memory)
input$ = 8
float get<2>(__m128) PROC ; get<2>, COMDAT
movss xmm0, DWORD PTR [rcx+8]
ret 0
float get<2>(__m128) ENDP ; get<2>
# gcc8.2 -O3 for x86-64 System V (arg in xmm0)
get2(float __vector(4)):
shufps xmm0, xmm0, 2 # with -msse4, we get unpckhps
ret
# clang7.0 -O3 for x86-64 System V (arg in xmm0)
get2(float __vector(4)):
unpckhpd xmm0, xmm0 # xmm0 = xmm0[1,1]
ret
Оптимизатор перемешивания clang упрощается до unpckhpd
, что быстрее на некоторых старых процессорах.К сожалению, он не заметил, что мог бы использовать movhlps xmm0,xmm0
, который также быстр и на 1 байт короче.