RSQRTSS 是否打破了对目标寄存器的依赖?
Does RSQRTSS break the dependency on the destination register?
使用 uiCA 我为以下代码生成了跟踪 table。
cvtsi2ss xmm0, eax
addss xmm0, xmm0
https://uica.uops.info/tmp/780bce9e56ee4a718d5369deb1326215_trace.html
您可以看到每个 cvtsi2ss
必须等待上一次迭代完成,因为它取决于 xmm0
.
的某些位 (32:127
)
但是,将 cvtsi2ss
更改为 rsqrtss
会有很大的不同。
rsqrtss xmm0, xmm1
addss xmm0, xmm0
https://uica.uops.info/tmp/8897a7d45c8348e68279aea4d0b18e15_trace.html
每个 rsqrtss
与前一个迭代并行执行。我不明白,因为 rsqrtss
产生一个位 32:127
不变的输出,就像 cvtsi2ss
,所以我认为它应该等待输出寄存器上的任何操作完成,就像 cvtsi2ss
做了。
看完答案后,我运行简单测试了一下,看来肯定是uiCA有bug了。
IACA 也无法捕获输出依赖项。
测试代码如有错误请指正
__asm__ (
R"(.section .text
.balign 16
noXor:
mov eax, 0x3f800000
movd xmm1, eax
rdtscp
shl rdx, 32
or rax, rdx
mov rdi, rax
mov ecx, 1 << 30
jmp noXor_loop
.balign 16
noXor_loop:
rsqrtss xmm0, xmm1
addss xmm0, xmm0
dec ecx
jnz noXor_loop
rdtscp
shl rdx, 32
or rax, rdx
sub rax, rdi
ret
.balign 16
yesXor:
mov eax, 0x3f800000
movd xmm1, eax
rdtscp
shl rdx, 32
or rax, rdx
mov rdi, rax
mov ecx, 1 << 30
jmp yesXor_loop
.balign 16
yesXor_loop:
xorps xmm0, xmm0
rsqrtss xmm0, xmm1
addss xmm0, xmm0
dec ecx
jnz yesXor_loop
rdtscp
shl rdx, 32
or rax, rdx
sub rax, rdi
ret)"
);
unsigned long long noXor(void);
unsigned long long yesXor(void);
#include <stdio.h>
int main() {
for (int i = 0; i < 4; ++i) {
printf("noXor: %llu yesXor: %llu\n", noXor(), yesXor());
}
return 0;
}
noXor: 4978836501 yesXor: 696810039
noXor: 4971780086 yesXor: 690780109
noXor: 4977293771 yesXor: 687404710
noXor: 5499602729 yesXor: 687954399
在真实硬件上测试,您将看到预期的结果:rsqrtss xmm0, xmm1
具有 4 周期延迟 作为一部分xmm0 -> xmm0 依赖链。 (在我的 Skylake 上)。
这是 UICA 中的一个错误。或者实际上在 https://uops.info/ data it uses - your trace includes a link to the rsqrtss
page on uops.info 中,我们可以看到他们只测量了 operand 2 → 1
情况下的延迟,没有 1 → 1
情况下的条目。编写该测试时,作者可能 copy/pasted rsqrtps
测试而忘记为输出依赖项添加测试。
如果没有在真实的 CPU 上进行自我测试,只有当有一项实际测试测量 1 → 1
的 rsqrtss
的零延迟时,您才会感到惊讶。如果没有这样的测试,正确的假设是缺少测试(因此 UICA 结果是错误的),而不是延迟实际上为零。
许多指令对任何 CPU 都没有输出依赖性,因此 https://uops.info/ 没有对它们全部进行测试是有道理的。我们宁愿 rsqrtps
延迟被列为 4
而不是 [0:4]
,除非有一些 CPU 是 non-zero.
当没有测试数据时,UICA 将假设没有输出依赖性是有道理的;对于没有输出依赖性的指令,这是正常的。
但是当然rsqrtss
应该分别测试从每个操作数到目的地的延迟。它是 non-zero,但至少理论上 CPU 允许合并目标的延迟转发是可能的,因此实际测试而不是假设它与正确的源相同是明智的。 (我不知道任何 x86 CPUs 其中单个 uop 允许延迟转发,因此来自不同输入的不同延迟通常只发生在 multi-uop 指令中。不像某些 ARM CPUs where他们的 FMA and/or 整数 MAC 单位允许延迟转发加数。)
顺便说一句,您的推理是正确的,rsqrtss
确实具有依赖性,除非硬件为 XMM regs partial-register 重命名。但是没有 real-word 硬件可以做到这一点。
PIII 和 Pentium-M 必须分别编写 XMM 的每个 64 位 一半 ,也许可以只写一半,但 rsqrtss
由于英特尔的 short-sighted 设计选择,保留了一半的低半部分不变。 (现在我很好奇 Pentium-M cvtsi2sd xmm0, eax
或 sqrtsd xmm0, xmm1
是否具有错误的输出依赖性。)但是当前 CPU 一次写入整个 XMM 寄存器。
AVX 版本 vrsqrtss
甚至需要一个额外的源操作数来合并,它可以与结果写入的目标分开。
使用 uiCA 我为以下代码生成了跟踪 table。
cvtsi2ss xmm0, eax
addss xmm0, xmm0
https://uica.uops.info/tmp/780bce9e56ee4a718d5369deb1326215_trace.html
您可以看到每个 cvtsi2ss
必须等待上一次迭代完成,因为它取决于 xmm0
.
32:127
)
但是,将 cvtsi2ss
更改为 rsqrtss
会有很大的不同。
rsqrtss xmm0, xmm1
addss xmm0, xmm0
https://uica.uops.info/tmp/8897a7d45c8348e68279aea4d0b18e15_trace.html
每个 rsqrtss
与前一个迭代并行执行。我不明白,因为 rsqrtss
产生一个位 32:127
不变的输出,就像 cvtsi2ss
,所以我认为它应该等待输出寄存器上的任何操作完成,就像 cvtsi2ss
做了。
看完答案后,我运行简单测试了一下,看来肯定是uiCA有bug了。
IACA 也无法捕获输出依赖项。
测试代码如有错误请指正
__asm__ (
R"(.section .text
.balign 16
noXor:
mov eax, 0x3f800000
movd xmm1, eax
rdtscp
shl rdx, 32
or rax, rdx
mov rdi, rax
mov ecx, 1 << 30
jmp noXor_loop
.balign 16
noXor_loop:
rsqrtss xmm0, xmm1
addss xmm0, xmm0
dec ecx
jnz noXor_loop
rdtscp
shl rdx, 32
or rax, rdx
sub rax, rdi
ret
.balign 16
yesXor:
mov eax, 0x3f800000
movd xmm1, eax
rdtscp
shl rdx, 32
or rax, rdx
mov rdi, rax
mov ecx, 1 << 30
jmp yesXor_loop
.balign 16
yesXor_loop:
xorps xmm0, xmm0
rsqrtss xmm0, xmm1
addss xmm0, xmm0
dec ecx
jnz yesXor_loop
rdtscp
shl rdx, 32
or rax, rdx
sub rax, rdi
ret)"
);
unsigned long long noXor(void);
unsigned long long yesXor(void);
#include <stdio.h>
int main() {
for (int i = 0; i < 4; ++i) {
printf("noXor: %llu yesXor: %llu\n", noXor(), yesXor());
}
return 0;
}
noXor: 4978836501 yesXor: 696810039
noXor: 4971780086 yesXor: 690780109
noXor: 4977293771 yesXor: 687404710
noXor: 5499602729 yesXor: 687954399
在真实硬件上测试,您将看到预期的结果:rsqrtss xmm0, xmm1
具有 4 周期延迟 作为一部分xmm0 -> xmm0 依赖链。 (在我的 Skylake 上)。
这是 UICA 中的一个错误。或者实际上在 https://uops.info/ data it uses - your trace includes a link to the rsqrtss
page on uops.info 中,我们可以看到他们只测量了 operand 2 → 1
情况下的延迟,没有 1 → 1
情况下的条目。编写该测试时,作者可能 copy/pasted rsqrtps
测试而忘记为输出依赖项添加测试。
如果没有在真实的 CPU 上进行自我测试,只有当有一项实际测试测量 1 → 1
的 rsqrtss
的零延迟时,您才会感到惊讶。如果没有这样的测试,正确的假设是缺少测试(因此 UICA 结果是错误的),而不是延迟实际上为零。
许多指令对任何 CPU 都没有输出依赖性,因此 https://uops.info/ 没有对它们全部进行测试是有道理的。我们宁愿 rsqrtps
延迟被列为 4
而不是 [0:4]
,除非有一些 CPU 是 non-zero.
当没有测试数据时,UICA 将假设没有输出依赖性是有道理的;对于没有输出依赖性的指令,这是正常的。
但是当然rsqrtss
应该分别测试从每个操作数到目的地的延迟。它是 non-zero,但至少理论上 CPU 允许合并目标的延迟转发是可能的,因此实际测试而不是假设它与正确的源相同是明智的。 (我不知道任何 x86 CPUs 其中单个 uop 允许延迟转发,因此来自不同输入的不同延迟通常只发生在 multi-uop 指令中。不像某些 ARM CPUs where他们的 FMA and/or 整数 MAC 单位允许延迟转发加数。)
顺便说一句,您的推理是正确的,rsqrtss
确实具有依赖性,除非硬件为 XMM regs partial-register 重命名。但是没有 real-word 硬件可以做到这一点。
PIII 和 Pentium-M 必须分别编写 XMM 的每个 64 位 一半 ,也许可以只写一半,但 rsqrtss
由于英特尔的 short-sighted 设计选择,保留了一半的低半部分不变。 (现在我很好奇 Pentium-M cvtsi2sd xmm0, eax
或 sqrtsd xmm0, xmm1
是否具有错误的输出依赖性。)但是当前 CPU 一次写入整个 XMM 寄存器。
AVX 版本 vrsqrtss
甚至需要一个额外的源操作数来合并,它可以与结果写入的目标分开。