【发布时间】:2018-05-29 19:33:59
【问题描述】:
我无法理解汇编中的某段代码。任务是使用 SSE 算法和 XMM 寄存器找到 2 个向量的点积。方法是一次读取 4 个浮点数的向量(意味着一个 xmm 寄存器将在一次迭代中保存四个)。最终结果是一个 xmm 寄存器,每个字节保存给定向量的乘积 (x1*y1 +...) 的总和。
我没有得到的是之后的部分。将这些“结束”字节全部相加所需的所有内容,基本上是对构成最终寄存器的 4 个字节进行求和。我试图在这方面找到一些东西,但没有占上风。我得到的超出了我的理解,我什至尝试在纸上写下每一个计算,没有什么意义。在突出显示的部分,计算实际总和并将其存储在xmm0 的最低字节中。欢迎对此提出任何见解。
.intel_syntax noprefix
.data
two: .int 2
.text
.global dot_product
############################################################################
##
## Function:
##
## void dot_product(float *x, float *y, int n, float *r);
##
## calculates the dot product of x and y (n lengths) and stores the result
## in r
##
## -- float * x -- rdi --
## -- float * y -- rsi --
## -- int n -- rdx --
## -- float * r -- rcx --
##
############################################################################
dot_product:
enter 0, 0
mov r8, rcx
mov r9, rdx
mov rax, 1
cpuid
test rdx, 0x2000000
jz not_supported
mov rdx, rsp
and rsp, 0xfffffffffffffff0
sub rsp, 512
fxsave [rsp]
mov rcx, r9
xorps xmm0, xmm0
next_four:
cmp rcx, 4
jb next_one
movups xmm1, [rsi]
movups xmm2, [rdi]
mulps xmm1, xmm2
addps xmm0, xmm1
add rsi, 16
add rdi, 16
sub rcx, 4
jmp next_four
next_one:
jrcxz finish
movss xmm1, [rsi]
movss xmm2, [rdi]
mulss xmm1, xmm2
addss xmm0, xmm1
add rsi, 4
add rdi, 4
dec rcx
jmp next_one
finish:
#**summing the 4 bytes giving the actual dot product**
movhlps xmm1, xmm0
addps xmm0, xmm1
movaps xmm1, xmm0
shufps xmm1, xmm1, 0b01010101
addss xmm0, xmm1
movss [r8], xmm0
fxrstor [rsp]
mov rsp, rdx
done:
leave
ret
not_supported:
mov rax, 1
mov rbx, 1
int 0x80
【问题讨论】:
-
这个循环瓶颈是
addps的延迟(每 3 或 4 个时钟一个迭代器),而不是吞吐量(1 或 0.5 个时钟),因为它只使用一个向量累加器。如果内存带宽不是瓶颈,使用多个累加器展开可以将 Skylake 的性能提高 4 倍。 -
顺便说一句,这个手写的 asm 优化得很差。顶部的 cmp/jb 以及 3 个 add/sub 和一个 jmp 是 lot 的循环开销。 (仍然不足以比
addps延迟瓶颈慢,但请参阅Why are loops always compiled into "do...while" style (tail jump)? 了解有关循环结构的更多信息。您可能最好在 C 中使用内在函数重写它并让编译器生成代码,或者只让编译器自动矢量化标量循环(使用 OpenMP,或使用-ffast-math以允许重新排序 FP 操作)。JRCXZ 比 cmp/jz 慢