我正在用Golang写一个汇编函数。为了简化,假设我要执行以下功能:
func sseSumOfMinimums (d1, d2 [2]float64) float64
它将计算d1 [0],d2 [0]的最小值以及d1 [1]和d2 [1]的最小值并计算总和
在汇编中,我这样做:
TEXT ·sseSum(SB), $0-40
MOVUPD d1+0(FP), X0 // loading d1 to X0
MOVUPD d2+16(FP), X1 // loading d1 to X1
MINPD X0, X1 // compute pair minimums and store to X1
MOVSD X1, X2 // move first min to X2
// How do I move second float of X1 to X3?
ADDSD X2, X3
MOVSD X3, ret+32(FP)
我缺少的部分是如何从X1提取第二个标量到X3
答案 0 :(得分:1)
Go不能保证堆栈对齐,因此您可以为minpd
使用内存源操作数吗?
此外,我对Go并不熟悉;它的float
是真正的IEEE binary64,大多数语言(包括x86 asm)都称为double
吗?奇怪的是,在源代码中看到float
而在汇编中使用了pd
(压缩双倍)指令。
为此,调用一个独立的手写asm函数的开销将比让编译器对一个单独的标量minsd
进行的开销更高。尤其是在Go糟糕的调用约定下,将args传递到内存中并将返回值存储到内存中。
具有LLVM或gcc后端的经过优化的Go编译器应使用内联代码完成工作,该内联代码比调用此函数具有更低的延迟和更低的吞吐量成本,即使进行以下优化。或者,如果您很幸运,编译器会为您使用minpd
。
但是对于实际问题,在minpd x0, x1
之后,您需要的是xmm1
的水平和。 Fastest way to do horizontal float vector sum on x86。
即使您只关心低64位,也应该使用movaps
复制xmm寄存器。 movsd x1, x2
合并到xmm2的低64位中,从而对旧值产生了错误的依赖关系,并耗费了shuffle uop。
minpd x0, x1
movhps x1, x0 // high 64 bits of xmm1 => low 64 of xmm0
addsd x1, x0
您可以使用movaps x1, x2
和unpckhpd x2,x2
,但这会花费额外的movapd
或movaps
,而使用movhps
可以避免。
({movaps
/ movups
短于movapd
,代码大小更小,否则在所有CPU上完全等效于movapd
/ movupd
,商店和reg-reg副本。)