几乎每个_ss 和_ps 内在/ 指令都有一个double 版本,带有_sd 或_pd 后缀。 (标量双精度或压缩双精度)。
例如,搜索(double in Intel's intrinsic finder 以查找将double 作为第一个参数的内部函数。或者只是弄清楚最佳 asm 是什么,然后在 insn ref 手册中查找这些指令的内在函数。除了 doesn't list all the intrinsics for movsd,因此在内部函数查找器中搜索指令名称通常是可行的。
re: 头文件:总是只包含<immintrin.h>。它包括所有英特尔 SSE/AVX 内在函数。
另请参阅ways to put a float into a vector 和sse 标签wiki,了解有关如何洗牌向量的链接。 (即Agner Fog's optimizing assembly guide中的shuffle指令表)
(请参阅下面的链接到一些有趣的编译器输出)
re: 你的序列
如果您确实想要合并两个向量,请仅使用 _mm_move_ss(或 sd)。
您没有说明m 是如何定义的。您使用a 作为浮点数和向量的变量名意味着向量中唯一有用的信息是float 参数。变量名冲突当然意味着它不能编译。
不幸的是,似乎没有任何方法可以将 float 或 double “投射”到上面 3 个元素中有垃圾的向量中,就像 __m128 -> __m256 一样:
__m256 _mm256_castps128_ps256 (__m128 a).我发布了一个关于内在函数限制的新问题:How to merge a scalar into a vector without the compiler wasting an instruction zeroing upper elements? Design limitation in Intel's intrinsics?
我尝试使用_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 进行存储/重新加载。
其他有趣的编译器输出from the float and double versions on the Godbolt compiler explorer
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 和 double 使用不同的策略,即使它们可以使用相同的策略。
在浮点运算之间使用像movq 这样的整数运算会导致额外的旁路延迟延迟。使用insertps 将输入寄存器的高位元素归零应该是浮点或双精度的最佳策略,因此所有编译器应该在 SSE4.1 可用时使用它。 xorps + blend 也很好,并且可以在比 insertps 更多的端口上运行。存储/重新加载可能是最糟糕的,除非我们在 ALU 吞吐量上遇到瓶颈,并且延迟无关紧要。