在 x86 ASM 中测试零通常哪个更快:"TEST EAX, EAX" 与 "TEST AL, AL"?

Which is generally faster to test for zero in x86 ASM: "TEST EAX, EAX" versus "TEST AL, AL"?

测试 AL 中的字节是否为零/非零通常哪个更快?

假设之前的 "MOVZX EAX, BYTE PTR [ESP+4]" 指令加载了一个零扩展字节参数到 EAX 的剩余部分,防止了我已经知道的组合值惩罚。

所以 AL=EAX 并且读取 EAX 没有部分寄存器惩罚。

凭直觉,仅检查 AL 可能会让您认为它更快,但我敢打赌,对于 >32 位寄存器的字节访问,需要考虑更多的惩罚问题。

任何 info/details 感谢,谢谢!

代码大小相等,所有 x86 CPUs AFAIK 上的性能也相等。

Intel CPUs(使用部分寄存器重命名)在写入 EAX 后绝对不会因为读取 AL 而受到惩罚。其他 CPU 也没有读取低字节寄存器的惩罚。

读取 AH 会对 Intel CPUs 造成不利影响,比如一些额外的延迟。 ()

一般来说,32 位操作数大小和 8 位操作数大小(低 8 位而不是高 8 位)速度相等,除了错误依赖或稍后的部分寄存器读取惩罚 写入一个8位寄存器。由于 TEST 仅读取寄存器,因此这不是问题。即使 add al, bl 也很好:该指令已经对两个寄存器具有输入依赖性,并且在 Sandybridge 系列上,对寄存器低字节的 RMW 不会单独重命名它。 (无论如何,Haswell 和后来的版本都不要单独重命名低字节寄存器)。

选择您喜欢的操作数大小。 8位和32位基本相等。选择只是人类可读性的问题。如果您稍后打算将该值作为 32 位整数使用,则选择 32 位。如果它在逻辑上仍然是一个 8 位值,并且您仅使用 movzx 作为 ARM ldrb 或 MIPS lbu 的 x86 等价物,那么使用 8 位是有意义的。

cmp al, imm 这样可以使用 no-modrm 短格式编码的指令有代码大小优势。 cmp al, 0 一些 旧的 CPUs(核心 2)上仍然比 test al,al 差,其中 cmp/jcc 宏融合不太灵活比 test/jcc 宏观融合。 ()


这些指令之间有一个区别:test al,al根据AL的高位(可以是非零)设置SFtest eax,eax 将始终清除 SF。如果你只关心 ZF 那么这没有什么区别,但是如果你在 SF 中的高位用于以后的分支或 cmovcc/setcc 那么你可以避免做第二个 test.


其他测试内存字节的方法:

如果您使用 setcc 或 cmovcc 而不是 jcc 分支使用标志结果,那么宏融合在下面的讨论中无关紧要。

如果以后还需要寄存器中的实际值,movzx/test/jcc几乎肯定是最好的。否则,您可以考虑进行内存目标比较。

cmp [mem], immediate 可以在 Intel 上微融合到 load+cmp uop,只要寻址模式不是 RIP 相关的。 (在 Sandybridge 系列上,索引寻址模式甚至在 Haswell 和更高版本上也将取消层压:参见 Micro fusion and addressing modes)。 Agner Fog 没有提到 AMD 是否有将 cmp/jcc 与内存操作数融合的限制。

;;; no downside for setcc or cmovcc, only with JCC on Intel
;;; unknown on AMD
    cmp byte [esp+4], 0       ; micro-fuses into load+cmp with this addressing mode
    jnz   ...                 ; breaks macro-fusion on SnB-family

我没有 AMD CPU 来测试 CMP 为 mem, immediate 时 Ryzen 或任何其他 AMD 是否仍然融合 cmp/jcc。现代 AMD CPUs do 通常会进行 cmp/jcc 和 test/jcc 融合。 (但不像 SnB 家族那样 add/sub/and/jcc 融合)。

cmp mem,imm / jcc(对比 movzx/test+jcc):

  • 更小的代码大小(以字节为单位)
  • 主流 Intel 上相同数量的前端/融合域 uops (2)。如果 cmp+load 的微融合是不可能的,这将是 3 个前端微指令,例如使用 RIP 相对寻址模式 + 立即数。或者在具有索引寻址模式的 Sandybridge 系列上,它会在解码后但在发送到后端之前 unlaminate 到 3 uops。

    优点:这在 Silvermont/Goldmont/KNL 或非常老的 CPUs 上仍然是 2,没有宏融合。 movzx/test/jcc 的主要优势在于宏融合,因此它落后于 CPUs 而不会发生这种情况。

  • 3 后端 uops(未融合的域 = 执行端口和调度程序中的 space 又名 RS)因为 cmp-immediate 不能与 JCC 宏融合在 Intel Sandybridge 系列 CPUs 上(在 Skylake 上测试)。微指令是加载、cmp 和一个单独的分支微指令。 (对比 movzx / test+jcc 为 2)。后端 uops 通常不是直接的瓶颈,但如果负载有一段时间没有准备好,它会在 RS 中占用更多 space,限制了这个乱序执行可以看到的距离.

cmp [mem], reg / jcc 可以宏+微融合到一个比较+分支uop中,所以非常好.如果您稍后在函数中需要一个归零寄存器,请先将其异或归零并将其用于内存上的单 uop 比较+分支。

    movzx  eax, [esp+4]       ; 1 uop (load-port only on Intel and Ryzen)
    test   al,al                     ; fuses with jcc
    jnz    ...                ; 1 uop

前端仍然是 2 微指令,但后端也只有 2 微指令。 test/jcc 宏融合在一起。不过,它的代码量更大。

如果您不是分支而是使用 cmovccsetcc 的 FLAGS 结果,那么使用 cmp mem, imm 没有任何缺点。只要您不使用相对于 RIP 的寻址模式(当还有立即数时它总是阻止微融合)或索引寻址模式,它就可以微融合。