有一个关于 C++ 强制转换语义的有趣问题(微软已经为您简要回答了),但它与您滥用_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*
输出 arg,不是需要结果的函数xmm0
。 (是的,这是一个用例extractps
指令,但可以说不是_mm_extract_ps
内在函数。 gcc 和 clang 使用extractps
优化时*out = get<2>(in)
,尽管 MSVC 错过了这一点并且仍然使用 shufps + movss。)
您显示的两个 asm 块只是将 xmm0 的低 32 位复制到某处,而没有转换为 int。你遗漏了重要的不同之处,只显示了无用复制的部分float
位模式从 xmm0 出来然后返回,以两种不同的方式(到寄存器或到内存)。movd
是未修改的位的纯副本,就像 movss 负载一样。
在你强制使用它之后,编译器会选择使用它extractps
根本不。通过寄存器并返回的延迟比存储/重新加载要低,但 ALU 微指令更多。
The (float const&)
尝试输入双关语确实包括从 FP 到整数的转换,您没有显示这一点。好像我们需要更多的理由来避免类型双关的指针/引用转换,这确实意味着不同的东西:(float const&)f 采用整数位模式(来自_mm_extract_ps
) as an int
并将其转换为float
.
我把你的代码在 Godbolt 编译器资源管理器上 https://godbolt.org/#z:OYLghAFBqd5TKALEBjA9gEwKYFFMCWALugE4A0BIEAZugHZEDKqAhgDbYgCMAdAEwAOAJzCALAFYADIIDsohaPIArHuXat6oVAFJ%2BAIT37yAZ3QBXUqi4ByPQGYC9VO3M4A1DvsBhAgFs/JyJSJ14kL1wdKQBBImw/AAcNOM8fIPcCCPcadnRWIndgbCIIAH1Sv24hDPoE8yIASk9ZQxj3dvdyyuqiRNSAERq6oi9W6I6MmncIAiaJ%2BYXFpfcAehWMr36pdz9sTRN3IiRsd2xOXcYMg45SPcwATxr3JNZrKPHF3oSBzoDSkyQ5hoOWwpQSJggX3IUNmo3aa1O7BMJwBQJBGQKJEOx3cACN0EQSH5eO8JrciJZ6L8/KVUAA3IgmEylGj2fiQxINUbvHSyfrvBEAWSYADVvO4ALQAcTp0zp2FQJCsHHYTQSrCZ2GupGATwAHgEpAL1gB1YhIZ4aJzuTDmPy49x6gBsYmyGqIbHY7HI7lYOqu7gY2JRRFeAGsfZpMBj3AAqTDoLWx9zKcwmAqCgDyIqYTHcuVY0YA7uaLAVUcD2E5gO8cnkCkUiOyulVBDUmryxmTipTCsUvN5%2BBEZvQufYxrz%2BTFawWG8VuKUS0dStg9cFXkRwbSGOmyhVW9kAh2WqSOukpptqSu16QN2CITQAj7uGOux1yb3aLP3Bh6Om9E6DQ0Ny058jyMR1vkfZEAuS5INe66Klu9AML%2Bu4ttUj5%2BMeb7tOePwVDSq6IUQ960E%2B7gviBHztB%2BpBUl%2B9aePwgHAeOPJgTENgNOoIA2BINjkPQfFSIJ6B8d4RhGO4ZiWNYzH2NwglECJ3E8WGIDSLxNhiIJfggGI9i8LIEj8BIghiCI/BmfwwjcEJqnkOJNiCSYIBSNCqk8XAsAoBgiQEJwFBUBA/kJIF2CkGg7ClPQ2BFqULrkDQgVxKQbkQLijm4k4fr3HxSnkP5FxEJm9DsPlNiieQOB%2BJowCcI5hC3IqBDym5VWCauCr1LYhVBGcjlVrit6kPc3hYH1ykhPpNhKTxGhaGgUkGOoBC4m5kA8egCREAQO58RKmb8JKMquRYVhcC%2B2kCQ5nVOXxzqun4Jh0qg7h0twwi8NwroQCaACSAByuBNBA%2BDEGQCn2e4E0BUFCn2E0kkGEYyleTxxyFpF1ALXxunkPpZm8C6ToWbIYiyNw9iGVI9j2Hd1XOa57meZ13mIBAfnoPDkWUNQYURVFLixfFiViMlqWRRlWX3Tl9B5QVgnFdgjBlRVTXxPVjX3c1Cp7e1jndagvVK5QjCDfdw2jeNk1m8E/hKwt9XLajq3DZtEDbbt%2B1/odx3nXJV14/xgnCfdzlPTsr3vZ932/dMgMg2DENKtDPpw%2BFCMOGIyMrfo6PsyHBP6fYUgk1IUgSE6shOvYLpSGIVcSIzYl8SzHkqUX5AaVp7B8fYYeOczbOiRz8A%2BcgaA81nfMhYLQUgMAgj8JL7BpTL2W5WNZsq2r5WVdVtXa7YR8EC1Btakbeo9XEZsDf3VvrTbE04PbM1O%2BoLu6G7xge/A3s9oHRsEdewkoTQqnAcRW8koXrIgJrJS6PAQ63XDkzR6ggnQShdIUVA71BACGmKnKGDgYaZyFtDPOv9C5j0xncHGXse6aQ8o/EuIB6a8Hplw7hPCnStwei5UwrMu5j3IJPLmIAyzDH5qFGeQs1DYEIEqNQRZbwJE/o/VBw8%2BKkPcHBR0mDsGumAHg9wBD%2BA0LUuQLGOAoqMNYXpEA1lOFSC%2BmIJ0DcpB10MhLNBbdBFuU7l5MRnNua82CgLORi8XD1VkJXNeG9qCy2qvLRWc1lY8xKurQ%2Bglj5aB1mfC%2BbUr73WNqbdJ5s4iP2qtbPKr8prQg/uk52S0f6GHdutT2gDfYdVAeAyBEoTTQNYLAzUCCLrWGQTdIeEcMFYJwTErQ7hZC8HLtscGSiSH8EUhnKJkUFL8Coe0guo8rEOMJgZMQnCXSyH4JXFiTo6bbP4SPQJpyGjqWYdpQeLz27vJDhY35giRFWPlOlX2BkgA%3D%3D看看你遗漏了什么。
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++ 中支持作为扩展)。当然在this在这种情况下,将你想要的向量元素类型双关为整数然后返回是可怕的。
仅适用于(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 使用 SSE3movshdup
将元素 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
or extractps
are for. 英特尔 SSE:为什么“_mm_extract_ps”返回“int”而不是“float”? https://stackoverflow.com/questions/5526658/intel-sse-why-does-mm-extract-ps-return-int-instead-of-float
A float
寄存器中的元素与向量的低位元素相同。高元素不需要归零。如果他们这样做了,你会想使用insertps
它可以根据立即数做xmm,xmm和零元素。
Use _mm_shuffle_ps
将你想要的元素带到寄存器的低位,然后is标量浮点数。 (你可以告诉 C++ 编译器_mm_cvtss_f32
)。这应该编译为shufps xmm0,xmm0,2
, without an extractps
or any 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
.)
If your fmm
在内存中,而不是寄存器中,那么希望编译器能够优化掉洗牌,然后movss xmm0, [mem]
。 MSVC 19.14 确实设法做到了这一点,至少对于堆栈情况下的 function-arg 来说是这样。我没有测试其他编译器,但 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 编译器资源管理器 https://godbolt.org/#z:OYLghAFBqd5TKALEBjA9gEwKYFFMCWALugE4A0BIEAZugHZEDKqAhgDbYgCMAdAEwAOAJzCALAFYADIIDsohaPIArHuXat6oVAFJ%2BAIT37yAZ3QBXUqi4ByPQGYC9VO3M4A1DvsBhAgFs/JyJSJ14kL1wdKQBBImw/AAcNOM8fIPcCCPcadnRWIndgbCIIAH1Sv24hDPoE8yIASk9ZQxj3dvdyyuqiRNSAERq6oi9W6I6MmncIAiaJ%2BYXFpfcAehWMr36pdz9sTRN3IiRsd2xOXcYMg45SPcwATxr3JNZrKPHF3oSBzoDSkyQ5hoOWwpQSJggX3IUNmo3aa1O7BMJwBQJBGQKJEOx3cACN0EQSH5eO8JrciJZ6L8/KVUAA3IgmEylGj2fiQxINUbvHSyfrvBEAWSYADVvO4ALQAcTp0zp2FQJCsHHYTQSrCZ2GupGATwAHgEpAL1gB1YhIZ4aJzuTDmPy49x6gBsYmyGqIbHY7HI7lYOqu7gY2JRRFeAGsfZpMBj3AAqTDoLWx9zKcwmAqCgDyIqYTHcuVY0YA7uaLAVUcD2E5gO8cnkCkUiOyulVBDUmryxmTipTCsUvN5%2BBEZvQufYxrz%2BTFawWG8VuKUS0dStg9cFXkRwbSGOmyhVW9kAh2WqSOukpptqSu16QN2CITQAj7uGOux1yb3aLP3Bh6Om9E6DQ0Ny058jyMR1vkfZEAuS5INe66Klu9AML%2Bu4ttUj5%2BMeb7tOePwVDSq6IUQ960E%2B7gviBHztB%2BpBUl%2B9aePwgHAeOPJgTENgNOoIA2BINjkPQfFSIJ6B8d4RhGO4ZiWNYzH2NwglECJ3E8WGIDSLxNhiIJfggGI9i8LIEj8BIghiCI/BmfwwjcEJqnkOJNiCSYIBSNCqk8XAsAoBgiQEJwFBUBA/kJIF2CkGg7ClPQ2BFqULrkDQgVxKQbkQLijm4k4fr3HxSnkP5FxEJm9DsPlNiieQOB%2BJowCcI5hC3IqBDym5VWCauCr1LYhVBGcjlVrit6kPc3hYH1ykhPpNhKTxGhaGgUkGOoBC4m5kA8egCREAQO58RKmb8JKMquRYVhcC%2B2kCQ5nVOXxzqun4Jh0qg7h0twwi8NwroQCaACSAByuBNBA%2BDEGQCn2e4E0BUFCn2E0kkGEYyleTxxyFpF1ALXxunkPpZm8C6ToWbIYiyNw9iGVI9j2Hd1XOa57meZ13mIBAfnoPDkWUNQYURVFLixfFiViMlqWRRlWX3Tl9B5QVgnFdgjBlRVTXxPVjX3c1Cp7e1jndagvVK5QjCDfdw2jeNk1m8E/hKwt9XLajq3DZtEDbbt%2B1/odx3nXJV14/xgnCfdzlPTsr3vZ932/dMgMg2DENKtDPpw%2BFCMOGIyMrfo6PsyHBP6fYUgk1IUgSE6shOvYLpSGIVcSIzYl8SzHkqUX5AaVp7B8fYYeOczbOiRz8A%2BcgaA81nfMhYLQUgMAgj8JL7BpTL2W5WNZsq2r5WVdVtXa7YR8EC1Btakbeo9XEZsDf3VvrTbE04PbM1O%2BoLu6G7xge/A3s9oHRsEdewkoTQqnAcRW8koXrIgJrJS6PAQ63XDkzR6ggnQShdIUVA71BACGmKnKGDgYaZyFtDPOv9C5j0xncHGXse6aQ8o/EuIB6a8Hplw7hPCnStwei5UwrMu5j3IJPLmIAyzDH5qFGeQs1DYEIEqNQRZbwJE/o/VBw8%2BKkPcHBR0mDsGumAHg9wBD%2BA0LUuQLGOAoqMNYXpEA1lOFSC%2BmIJ0DcpB10MhLNBbdBFuU7l5MRnNua82CgLORi8XD1VkJXNeG9qCy2qvLRWc1lY8xKurQ%2Bglj5aB1mfC%2BbUr73WNqbdJ5s4iP2qtbPKr8prQg/uk52S0f6GHdutT2gDfYdVAeAyBEoTTQNYLAzUCCLrWGQTdIeEcMFYJwTErQ7hZC8HLtscGSiSH8EUhnKJkUFL8Coe0guo8rEOMJgZMQnCXSyH4JXFiTo6bbP4SPQJpyGjqWYdpQeLz27vJDhY35giRFWPlOlX2BkgA%3D%3D、MSVC、clang 和 gcc 的 asm 输出:
;; 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
,在某些旧 CPU 上速度更快。不幸的是它没有注意到它可以使用movhlps xmm0,xmm0
,速度也很快,并且短了 1 个字节。