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 → 1rsqrtss 的零延迟时,您才会感到惊讶。如果没有这样的测试,正确的假设是缺少测试(因此 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, eaxsqrtsd xmm0, xmm1 是否具有错误的输出依赖性。)但是当前 CPU 一次写入整个 XMM 寄存器。

AVX 版本 vrsqrtss 甚至需要一个额外的源操作数来合并,它可以与结果写入的目标分开。