目标寄存器的关键路径上只有 1 uop / 1c 延迟:
# target=rax extra source=rcx
mov edx, ecx ; no latency
and edx, 0 ; BMI1 ANDN could mov+and in 1 uop, port 1 or 5 only on SnB-family (Ryzen: any)
or rax, rdx
与零在任何 CPU 上都不是特殊情况下的 a dep-breaking zeroing idiom,AFAIK。
前端微指令:3 个(或 2 个 BMI1)。延迟:
- 从 rcx 到 rax:2c(使用 mov-elimination 或 BMI1)。
- 从 rax(输入)到 rax(输出):1c
对于一个归零的寄存器,如果可以将所有的 dep 链耦合到一个寄存器中(不像 ANDN 版本,它只读取一个全为寄存器):
and edx, ecx # 0 &= ecx
or rax, rdx # rax |= 0
测试函数的延迟(不是吞吐量),但仍重复为其提供相同的输入:
.loop:
call func ; arg in RDI, return in RAX
mov rdi, rbx ; arg for next iter, off the critical path
and eax, 0 ; 1c latency
or rdi, rax ; 1c latency
jmp .loop
如果函数是纯函数,我们可以做 1c / 1uop
实际上它只需要为给定的输入返回一个已知值。如果其杂质仅限于具有其他副作用/输出,这也有效。
在得到结果后不要进行两次 XOR,而是进行设置,这样我们就已经有了一个 XOR,我们只需再用一个 XOR 就可以解读它。或者使用加法,因为 LEA 允许我们在一条指令中进行复制和添加,从而保存一个不会在关键路径上的 mov。
mov rdi, rbx ; original input
call func
sub rbx, rax ; RBX = input - output
.loop:
call func
lea rdi, [rbx + rax] ; RDI = (input-output) + output = input
jmp .loop
@RossRidge 的建议是在 SnB 系列 CPU 上只有 1 uop,但只能在端口 1 上运行:
shld rax, rcx, 0
3c 延迟,HSW/SKL 上的端口 1 为 1 uop。 Agner Fog 报告 IvB 延迟为 1c,而 HSW/BDW/SKL 延迟为 3c。
shld r,r,i 在旧版 Intel 上为 2 uops,在 AMD 上显着降低,例如 Piledriver / Ryzen 上的 6 uops / 3c 延迟。
请注意,instlatx64 报告了 Haswell/Skylake 上 shld/shrd 的 1c 延迟/0.5c 吞吐量(如单寄存器移位),但我测试了自己,它绝对是 3c 延迟/1c 吞吐量。 Reported as an instlatx64 bug on their github page.
SHLD 也可以用于复制依赖于另一个的 32 位寄存器。例如@BeeOnRope 描述了希望在 RDI 中使用相同的输入值重复调用函数,但依赖于 RAX 中的结果。如果我们只关心 EDI,那么
; RBX = input<<32
call func
mov edi, eax ; 0 latency with mov-elimination
shld rdi, rbx, 32 ; EDI = the high 32 bits of RBX, high bits of RDI = old EDI.
当然,这与不需要 mov-elimination 的 this 相比毫无意义
call func
mov rdi, rbx ; off critical path
shld rdi, rax, 0 ; possibly 1c latency on SnB / IvB. 3 on HSW/SKL
修改@DavidWholford 的建议也有效:
test ecx,ecx ; CF=0, with a false dependency on RCX
adc rax, 0 ; dependent on CF
Haswell/Broadwell/Skylake 和 AMD 上的 2 微指令。英特尔 P6 系列上的 3 微指令,可能还有 SnB/IvB。延迟:
- 从 rcx 到 rax:在 HSW 上为 2c 及更高版本,在 2-uop adc 上为 3
- 从 rax 到 rax:1c 在 HSW 及更高版本上,2 使用 2-uop adc
Haswell 和更早版本的 ADC 通常为 2 uop,但 adc 立即为 0 在 Haswell 上是特殊情况,仅为 1 uop / 1c。 adc eax,0 在 Core 2 上总是 2c 延迟。第一个具有这种优化的 uarch 可能是 SnB,但希望我们能在 Which Intel microarchitecture introduced the ADC reg,0 single-uop special case? 上得到答案
test 不管值如何都会清除 CF,但我认为(未经测试)CF 仍然依赖于源寄存器。如果没有,那么也许使用 TEST / ADOX 在 Broadwell 及以后可能有用。 (因为 CF 在大多数 CPU 上是单独重命名的,但 OF 可能只是与 ZF / SF 和其他依赖于 AND 结果的标志相同的捆绑包的一部分。)