代码大小相同,所有 x86 CPU AFAIK 上的性能也相同。
Intel CPU(带有部分寄存器重命名)在写入 EAX 后绝对不会因为读取 AL 而受到惩罚。其他 CPU 也不会因为读取低字节寄存器而受到惩罚。
读取 AH 会对 Intel CPU 造成不利影响,例如一些额外的延迟。 (How exactly do partial registers on Haswell/Skylake perform? Writing AL seems to have a false dependency on RAX, and AH is inconsistent)
通常 32 位操作数大小和 8 位操作数大小(低 8 位而不是高位 8 位)的速度相同,除了 写入的错误依赖性或后来的部分寄存器读取惩罚 em> 一个 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 在一些 旧 CPU(Core 2)上仍然比 test al,al 差,其中 cmp/jcc 宏融合不如 test/jcc 宏融合灵活。 (Test whether a register is zero with CMP reg,0 vs OR reg,reg?)
这些指令之间有一个区别:test al,al 根据 AL 的高位(可以是非零)设置 SF。 test 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 CPU 做 通常做 cmp/jcc 和 test/jcc 融合。 (但不能像 SnB 家族那样添加/sub/and/jcc 融合)。
cmp mem,imm/jcc(对比movzx/test+jcc):
- 更小的代码大小(以字节为单位)
-
在主流英特尔上具有相同数量的前端/融合域微指令 (2)。如果 cmp+load 的微融合是不可能的,那么这将是 3 个前端 uops,例如使用 RIP 相对寻址模式 + 立即。或者在具有索引寻址模式的 Sandybridge 系列上,它会在解码后但在发送到后端之前解压到 3 uop。
优势:在 Silvermont/Goldmont / KNL 或没有宏融合的非常旧的 CPU 上,这仍然是 2。 movzx/test/jcc 的主要优势在于宏融合,因此它在不会发生这种情况的 CPU 上落后。
3 个后端 uops(未融合域 = 调度程序中的执行端口和空间,即 RS),因为 cmp-immediate 无法与英特尔 Sandybridge 系列 CPU 上的 JCC 进行宏融合(在 Skylake 上测试)。 uop 是 load、cmp 和单独的分支 uop。 (movzx / test+jcc 为 2)。后端 uops 通常不是直接的瓶颈,但如果负载暂时没有准备好,它会占用 RS 中的更多空间,从而限制了这种无序执行可以看到的更远距离。
cmp [mem], reg / jcc 可以宏 + 微融合成一个比较 + 分支微指令,非常棒。如果您在函数后面需要一个归零寄存器,请先对其进行异或归零,然后将其用于内存上的单微指令比较+分支。
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 宏融合在一起。不过,它会花费更多的代码大小。
如果您不是分支而是使用cmovcc 或setcc 的FLAGS 结果,则使用cmp mem, imm 没有任何缺点。只要您不使用 RIP 相对寻址模式(当还有立即数时总是阻止微融合)或索引寻址模式,它就可以进行微融合。