tru*_*der 6 c++ assembly sse intrinsics visual-c++
VS2019,发布,x86.
template <int i> float get() const {
int f = _mm_extract_ps(fmm, i);
return (float const&)f;
}
Run Code Online (Sandbox Code Playgroud)
使用return (float&)f;编译器时使用
extractps m32, ...
movss xmm0, m32
Run Code Online (Sandbox Code Playgroud)
.正确的结果
使用return (float const&)f;编译器时使用
extractps eax, ...
movd xmm0, eax
Run Code Online (Sandbox Code Playgroud)
.错误的结果
T&和T const&首先是T然后是const的主要思想.Const只是程序员的某种协议.你知道你可以解决它.但汇编代码中没有任何const,但是类型为float IS.我认为对于float和float const而言它必须是汇编中的浮点表示(cpu寄存器).我们可以使用中间int reg32,但最终解释必须是float.
而此时它看起来像回归,因为这之前工作正常.并且在这种情况下使用float也绝对是奇怪的,因为我们不应该考虑浮动const和安全性而是浮动的临时变量并且确实值得怀疑.
微软回答:
嗨Truthfinder,感谢自成一体的复制品.碰巧,这种行为实际上是正确的.正如我的同事@Xiang Fan [MSFT]在内部电子邮件中所述:
由[a c-style cast]执行的转换尝试以下序列:(4.1) - const_cast(7.6.1.11),(4.2) - static_cast(7.6.1.9),(4.3) - static_cast后跟const_cast ,(4.4) - reinterpret_cast(7.6.1.10)或(4.5) - reinterpret_cast后跟const_cast,
如果转换可以用上面列出的多种方式解释,则使用列表中首先出现的解释.
所以在你的情况下,(const float&)被转换为static_cast,其效果是"初始化表达式被隐式转换为类型为"cv1 T1"的prvalue.应用临时实现转换并将引用绑定到结果".
但在另一种情况下,(float&)被转换为reinterpret_cast,因为static_cast无效,这与reinterpret_cast(&operand)相同.
您正在观察的实际"错误"是一个强制转换:"将浮点型值"1.0"转换为等效的int-typed值"1"",而另一个强制转换说"将1.0的位表示形式转换为一个浮点数,然后将这些位解释为int".
出于这个原因,我们建议不要使用c风格的演员表.
谢谢!
MS论坛链接:https://developercommunity.visualstudio.com/content/problem/411552/extract-ps-intrinsics-bug.html
有任何想法吗?
PS我真正想要的是什么:
float val = _mm_extract_ps(xmm, 3);
Run Code Online (Sandbox Code Playgroud)
在手动汇编中我可以写:extractps val, xmm0, 3其中val是float 32内存变量.只有一个!指令.我希望在编译器生成的汇编代码中看到相同的结果.没有洗牌或任何其他过度指示.最不可接受的案例是:extractps reg32, xmm0, 3; mov val, reg32.
关于T&和T const&的观点:对于两种情况,变量的类型必须是相同的.但现在float&将m32解释为float32并将float const&m32解释为int32.
int main() {
int z = 1;
float x = (float&)z;
float y = (float const&)z;
printf("%f %f %i", x, y, x==y);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
出:0.000000 1.000000 0
那真的好吗?
最好的问候,Truthfinder
Pet*_*des 10
有一个关于C++强制语义的一个有趣的问题(微软已经为你简要回答了这个问题),但它与你的滥用相混淆,_mm_extract_ps导致首先需要一个类型双关语. (并且只显示相同的asm,省略int-> float转换.)如果有人想在另一个答案中扩展标准ese,那就太好了.
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);
}
Run Code Online (Sandbox Code Playgroud)
如果你实际上有一个内存目的地用例,那么你应该看一个asm来获取一个float*输出arg的函数,而不是一个需要结果的函数xmm0.(是的,这是extractps指令的用例,但可以说不是_mm_extract_ps内在函数.extractps在优化时使用gcc和clang *out = get<2>(in),尽管MSVC错过了它并且仍然使用shufps + movss.)
您显示的两个asm块都只是在某处复制xmm0的低32位,而没有转换为int.你忽略了重要的不同,只显示了那些只是无用地将float位模式从xmm0 复制出来然后以2种不同方式(寄存器或存储器)复制的部分. movd是未经修改的比特的纯副本,就像movss加载一样.
这是编译器的选择,在你强行使用它之后使用它extractps.通过寄存器返回比存储/重新加载更低的延迟,但更多的ALU uops.
(float const&)类型双关语的尝试确实包括从FP到整数的转换,您没有显示.好像我们需要更多的理由来避免指针/引用转换为类型惩罚,这确实意味着不同的东西:(float const&)f将整数位模式(from _mm_extract_ps)作为aint并将其转换为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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
显然,MSVC确实为类型惩罚定义了指针/引用转换的行为.普通的ISO C++没有(严格别名UB),其他编译器也没有.使用memcpy输入-双关语,或联合(其GNU C和用C MSVC支持++作为扩展名).当然,在这种情况下,对你想要的整数和向后的向量元素进行打字是非常糟糕的.
只有(float &)fgcc警告严格别名违规. 并且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;
^
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
_mm_extract_ps用于此你的两个版本都很糟糕,因为这不是什么_mm_extract_ps或extractps不适合. 英特尔SSE:为什么`_mm_extract_ps`返回`int`而不是`float`?
float寄存器中的A 与向量的低元素相同.高元素不需要归零.如果他们这样做,你会想要使用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);
}
Run Code Online (Sandbox Code Playgroud)
(我跳过使用,_MM_SHUFFLE(0,0,0,i)因为它等于i.)
如果你fmm在内存中,而不是一个寄存器,那么希望编译器能够优化掉洗牌movss xmm0, [mem].MSVC 19.14确实设法做到这一点,至少对于堆栈案例中的函数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);
}
Run Code Online (Sandbox Code Playgroud)
来自Godbolt编译器资源管理器,来自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>
Run Code Online (Sandbox Code Playgroud)
# 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
Run Code Online (Sandbox Code Playgroud)
# clang7.0 -O3 for x86-64 System V (arg in xmm0)
get2(float __vector(4)):
unpckhpd xmm0, xmm0 # xmm0 = xmm0[1,1]
ret
Run Code Online (Sandbox Code Playgroud)
clang的shuffle优化器简化为unpckhpd,在某些旧CPU上更快.不幸的是,它没有注意到它可以使用movhlps xmm0,xmm0,这也很快,1个字节更短.
我的观点
T&和T const&:对于两种情况,变量的类型必须是相同的.
正如微软的支持试图解释的那样,这些并不相同.这就是C++的工作原理.
您正在使用C风格的强制转换( ... ),在C++中分解为一系列尝试以降低安全性的顺序使用不同的C++强制转换:
const_caststatic_caststatic_cast后跟aconst_castreinterpret_castreinterpret_cast后跟aconst_cast在的情况下(float const&) b(其中,b是int):
const_cast<float const&>(b);- 没有运气(float对比int)static_cast<float const&>(b);- 瞧!(在隐式标准转换为b临时标记之后float- 记住C++允许自己每个表达式隐式执行两个标准和一个用户定义的转换)在的情况下(float&) b(其中,再次b是int):
const_cast<float&>(b);- 没有运气static_cast<float&>(b);- 没有运气(在隐式标准转换为b临时之后float,它不会绑定到非const左值引用)const_cast<float&>(static_cast<float&>(b));- 没有运气reinterpret_cast<float&>(b);- 瞧!严格别名规则1,这是一个演示此行为的示例:
#include <iostream>
int main() {
float a = 1.2345f;
int b = reinterpret_cast<int&>(a); // this type-pun is built into _mm_extract_ps
float nc = (float&)b;
float cc = (float const&)b;
float rc = reinterpret_cast<float&>(b);
float sc = static_cast<float const&>(b);
std::cout << "a=" << a << " b=" << b << std::endl;
std::cout << "nc=" << nc << " cc=" << cc << std::endl;
std::cout << "rc=" << rc << " sc=" << sc << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
打印:
a=1.2345 b=1067320345
nc=1.2345 cc=1.06732e+09
rc=1.2345 sc=1.06732e+09
Run Code Online (Sandbox Code Playgroud)
这就是为什么你不应该在C++中使用C风格的强制转换.少打字,但很多更头痛.
也不要使用_mm_extract_ps-为什么它返回一个原因int是因为extractps指令拷贝float到通用寄存器-这是不是你想要的,因为要使用一个float必须被复制回一个浮点寄存器.这样做是浪费时间.正如Peter Cordes所解释的那样,使用_mm_cvtss_f32(_mm_shuffle_ps())它来编译一条指令.
1从技术上讲,reinterpret_cast用于规避C++类型系统(又称类型惩罚)是ISO C++中未定义的行为.但是,MSVC将此规则放宽为编译器扩展.因此代码是正确的,只要它是用MSVC或其他可以关闭严格别名规则的地方编译的(例如-fno-strict-aliasing).在没有陷入严格混叠陷阱的情况下,标记双关语的方法是通过memcpy().
| 归档时间: |
|
| 查看次数: |
299 次 |
| 最近记录: |