在 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 中的字节是否为零/非零通常哪个更快?
TEST EAX, EAX
TEST 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的高位(可以是非零)设置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 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 宏融合在一起。不过,它的代码量更大。
如果您不是分支而是使用 cmovcc
或 setcc
的 FLAGS 结果,那么使用 cmp mem, imm
没有任何缺点。只要您不使用相对于 RIP 的寻址模式(当还有立即数时它总是阻止微融合)或索引寻址模式,它就可以微融合。
测试 AL 中的字节是否为零/非零通常哪个更快?
TEST EAX, EAX
TEST 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的高位(可以是非零)设置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 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 宏融合在一起。不过,它的代码量更大。
如果您不是分支而是使用 cmovcc
或 setcc
的 FLAGS 结果,那么使用 cmp mem, imm
没有任何缺点。只要您不使用相对于 RIP 的寻址模式(当还有立即数时它总是阻止微融合)或索引寻址模式,它就可以微融合。