SSE XMM点积说明
SSE XMM dot product explanation
我无法理解汇编中的某段代码。任务是使用 SSE 算法和 XMM 寄存器找到 2 个向量的点积。该方法是一次读取向量 4 个浮点数(这意味着一个 xmm 寄存器将在一次迭代中容纳四个)。最终结果是一个 xmm 寄存器,每个字节包含给定向量的乘积 (x1*y1 +...) 之和。
我不明白的是后面的部分。将这些 'end' 字节加起来所需的全部内容,基本上是将 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/addss 指令在 xmm0 中添加 4 个压缩浮点数。首先,它将 2 个最高的压缩浮点数复制到 xmm1 的低浮点数,因此 xmm0 + xmm1 可以用一条指令进行两次加法。 2 个高浮点数是 "do-not-care"。重复使用 shufps 将剩余浮点数中最高的复制到最低位置。将 shufps 的直接 'selector' 视为每个目标词的数组索引。唯一重要的是低两位,等于索引 1,移动 1->0。其他的都只是占位符。然后,只需添加一次即可。
xmm0: D | C | B | A
+
xmm1: X | X | D | C (movhlps xmm0)
-------------------
= X | X | B+D | A+C
xmm0: X | X | B+D | A+C
+
xmm1: X | X | X | B+D (shufps xmm0)
-----------------------
= X | X | X | A + B + C + D
这里,X 表示 "do not care"。最后,总和位于最低位置,将被 movss 提取。
2条加法指令,驻留在XMM寄存器中。否则,您需要 3 个,并有更明确的动作。
更多 shufps 详细信息:
将 0b01010101 值拆分为 4 个二进制索引:01 | 01 | 01 | 01,十进制为 1 | 1 | 1 | 1、每个索引从出处中选择出处(以词为单位)。正如文档所述,对于较高的 2 个词,这会变得更加复杂,但我们不关心这些。结果是将 word1 复制到 word0 和 word1,因为两个低选择器都是 1.
编辑:
HADDPS 是另一种可能的实现方式,添加邻居。两个 HADDPS 依次处理最终的总和。知道哪个更快的唯一方法是在您的目标处理器上进行基准测试,而不是最后一块对函数的整体速度影响很大。
我无法理解汇编中的某段代码。任务是使用 SSE 算法和 XMM 寄存器找到 2 个向量的点积。该方法是一次读取向量 4 个浮点数(这意味着一个 xmm 寄存器将在一次迭代中容纳四个)。最终结果是一个 xmm 寄存器,每个字节包含给定向量的乘积 (x1*y1 +...) 之和。
我不明白的是后面的部分。将这些 'end' 字节加起来所需的全部内容,基本上是将 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/addss 指令在 xmm0 中添加 4 个压缩浮点数。首先,它将 2 个最高的压缩浮点数复制到 xmm1 的低浮点数,因此 xmm0 + xmm1 可以用一条指令进行两次加法。 2 个高浮点数是 "do-not-care"。重复使用 shufps 将剩余浮点数中最高的复制到最低位置。将 shufps 的直接 'selector' 视为每个目标词的数组索引。唯一重要的是低两位,等于索引 1,移动 1->0。其他的都只是占位符。然后,只需添加一次即可。
xmm0: D | C | B | A
+
xmm1: X | X | D | C (movhlps xmm0)
-------------------
= X | X | B+D | A+C
xmm0: X | X | B+D | A+C
+
xmm1: X | X | X | B+D (shufps xmm0)
-----------------------
= X | X | X | A + B + C + D
这里,X 表示 "do not care"。最后,总和位于最低位置,将被 movss 提取。
2条加法指令,驻留在XMM寄存器中。否则,您需要 3 个,并有更明确的动作。
更多 shufps 详细信息:
将 0b01010101 值拆分为 4 个二进制索引:01 | 01 | 01 | 01,十进制为 1 | 1 | 1 | 1、每个索引从出处中选择出处(以词为单位)。正如文档所述,对于较高的 2 个词,这会变得更加复杂,但我们不关心这些。结果是将 word1 复制到 word0 和 word1,因为两个低选择器都是 1.
编辑: HADDPS 是另一种可能的实现方式,添加邻居。两个 HADDPS 依次处理最终的总和。知道哪个更快的唯一方法是在您的目标处理器上进行基准测试,而不是最后一块对函数的整体速度影响很大。