几乎每一个_ss
and _ps
内在/指令有一个double
版本带有_sd
or _pd
后缀。 (标量双精度或压缩双精度)。
例如,搜索找到需要的内在函数double
作为第一个参数。或者只是找出最佳的 asm 是什么,然后在 insn 参考手册中查找这些指令的内在函数。除了它没有列出所有内在函数movsd http://www.felixcloutier.com/x86/MOVSD.html,因此在内在函数查找器中搜索指令名称通常是有效的。
回复:头文件:总是只包含<immintrin.h>
。它包括所有英特尔 SSE/AVX 内在函数。
也可以看看方法来放置一个float变成一个向量 https://stackoverflow.com/a/11766098/224132,以及sse /questions/tagged/sse标记 wiki 以获取有关如何随机排列向量的链接。 (即洗牌指令表Agner Fog 的优化装配指南 http://www.agner.org/optimize/)
(请参阅下面的 godbolt 链接到一些有趣的编译器输出)
回复:你的序列
仅使用_mm_move_ss
(或 sd)如果你真的想合并两个向量。
你没有展示如何m
被定义为。您的使用a
作为浮点数和向量的变量名称意味着向量中唯一有用的信息是float
arg。变量名冲突当然意味着它无法编译。
不幸的是,似乎没有任何方法可以“投射”float
or double
变成一个向量,上面 3 个元素中有垃圾,就像 for__m128
-> __m256
:
。我发布了一个关于内在函数的限制的新问题:如何将标量合并到向量中,而不会让编译器浪费指令将上部元素归零?英特尔内在函数的设计限制? https://stackoverflow.com/questions/39318496/how-to-merge-a-scalar-into-a-vector-without-the-compiler-wasting-an-instruction
我尝试使用_mm_undefined_ps()
为了实现这一点,希望这能告诉编译器它可以将传入的高垃圾留在原处,在
// don't use this, it doesn't make better code
__m128d double_to_vec_highgarbage(double x) {
__m128d undef = _mm_undefined_pd();
__m128d x_zeroupper = _mm_set_sd(x);
return _mm_move_sd(undef, x_zeroupper);
}
但 clang3.8 将其编译为
# clang3.8 -O3 -march=core2
movq xmm0, xmm0 # xmm0 = xmm0[0],zero
ret
所以没有优势,仍然将上半部分归零,而不是将其编译为只是ret
。 gcc 实际上会生成非常糟糕的代码:
double_to_vec_highgarbage: # gcc5.3 -march=nehalem
movsd QWORD PTR [rsp-16], xmm0 # %sfp, x
movsd xmm1, QWORD PTR [rsp-16] # D.26885, %sfp
pxor xmm0, xmm0 # __Y
movsd xmm0, xmm1 # tmp93, D.26885
ret
_mm_set_sd
似乎是将标量转换为向量的最佳方法。
__m128d double_to_vec(double x) {
return _mm_set_sd(x);
}
clang 将其编译为movq xmm0,xmm0
, gcc 到存储/重新加载-march=generic
.
其他有趣的编译器输出来自float and doubleGodbolt 编译器浏览器上的版本 http://gcc.godbolt.org/#compilers:!((compiler:g530,options:'-xc+-std%3Dgnu11+-Wall+-Wextra++-O3+-fverbose-asm+-march%3Dnehalem',sourcez:MQSwdgxgNgrgJgUwAQB4QFt3gC4CdwB0AFgHwBQZA%2BpegIwBMAHEgGZQD2Ahtpdu5QDcEEABRsu2JAA8AlEgDeZJElwJsMXGCQ10lAM5r9ekbIDcZAL4VqdJqw7de/IREpEQAcyIfOuAEacHghiDpKyCkraNAzMMGCILEgAvNqYlHEJ4AhwlAAOxjLmyjYx0pQAXgi47DC5uVXJqboGPHrGZpGq6ppNNOxCRiIZCCwANGWV1bX1uIWWFFTRduKOfILClA5wIRLScorKXRpaOptcOW0iAGQdVou2zACyAJ4AYnEQ2CDsYDvcSM8ZGQDsoog8kOhGqcWpN%2BPkRHNQWDSpwoWl0P0EIN0ONoYZLoDESo1MckJwiqCAPSUuDsJAAdyI3AQQlwSBAkhAegZHKISAA5Jx%2BfMyNSxeKJcpaTA/FBkKy9N8wAAuYm5KCcCDINqU/I87B8vRwXVwUWUpB6djoZBwEAsFhVBCQBB6VVmpDQThgDxIAC06F8ECISSCYCqIAgFqtagwLqQMAM3NUHl9yYh/QAjkg%2BEhYdmiMhpg0mVAWO7GY72ZJbXAwPzJAnkBiBHBWOw2QbtZxrUhchpcuwDOytJ37LsFUrrEtGK3pbKsWsXCI53K9hFDiSenjWttblOHrOavOnOtXO4vD5/IFgivkOEQSUmK3hokUqcX1kcrltkTHzOJlUNR1A0b5pC0%2Bi7kSRxbuimIQUM8QjOMUgVIBRazOYdz3DEh4ynKJ4uGcnDbLea4gtBJxpFs8E3HMdx/rhx4vO8kBfD8y5HqugLrqCDEQmizRqLCeQ/hSxTTq2qKgbozZYkaIg4r04HyYSFIUWSYlINStIMky2Asg0HLsty9K8gKQqWEAAAA%3D)),filterAsm:(commentOnly:!t,directives:!t,intel:!t,labels:!t),version:3
float_to_vec: # gcc 5.3 -O3 -march=core2
movd eax, xmm0 # x, x
movd xmm0, eax # D.26867, x
ret
float_to_vec: # gcc5.3 -O3 -march=nehalem
insertps xmm0, xmm0, 0xe # D.26867, x
ret
double_to_vec: # gcc5.3 -O3 -march=nehalem. It could still have use movq or insertps, instead of this longer-latency store-forwarding round trip
movsd QWORD PTR [rsp-16], xmm0 # %sfp, x
movsd xmm0, QWORD PTR [rsp-16] # D.26881, %sfp
ret
float_to_vec: # clang3.8 -O3 -march=core2 or generic (no -march)
xorps xmm1, xmm1
movss xmm1, xmm0 # xmm1 = xmm0[0],xmm1[1,2,3]
movaps xmm0, xmm1
ret
double_to_vec: # clang3.8 -O3 -march=core2, nehalem, or generic (no -march)
movq xmm0, xmm0 # xmm0 = xmm0[0],zero
ret
float_to_vec: # clang3.8 -O3 -march=nehalem
xorps xmm1, xmm1
blendps xmm0, xmm1, 14 # xmm0 = xmm0[0],xmm1[1,2,3]
ret
所以 clang 和 gcc 都使用不同的策略float
vs. double
,即使他们可以使用相同的策略。
使用整数运算,例如movq
浮点运算之间的操作会导致额外的旁路延迟延迟。使用insertps
将输入寄存器的高位元素清零应该是 float 或 double 的最佳策略,因此所有编译器should当 SSE4.1 可用时使用它。 xorps + Blend 也很好,并且比 insertps 可以在更多端口上运行。存储/重新加载可能是最糟糕的,除非我们在 ALU 吞吐量上遇到瓶颈,并且延迟并不重要。