考虑以下x86程序集:
; something that sets rax
mov rcx, [rdi]
xor rax, rcx
xor rax, rcx
在序列的末尾,rax
的值与输入时的值相同,但是从CPU的角度来看,其值取决于从内存加载的值进入rcx
。特别是,rax
的后续使用要等到该加载和两条xor
指令完成后才能开始。
是否有任何方法可以比两个{xor
序列更有效地实现此效果,例如,使用单个单向单周期等待时间指令?如果需要在序列之前设置一次恒定值(例如,将寄存器清零),就可以了。
答案 0 :(得分:4)
目标寄存器的关键路径上只有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 AFAIK上,AND为零的情况都不是a dep-breaking zeroing idiom的特殊情况。
前端微指令:3(或BMI1为2)。延迟时间:
如果寄存器为零,则可以将所有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
实际上,它只需要为给定的输入返回一个已知值。如果其杂质被限制为具有其他副作用/输出,这也将起作用。
设置结果,而不是两次获取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 oups,在AMD上显着降低,例如在PILEDRIVER / Ryzen上为6 oups / 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.
相对于此,这当然是没有意义的,不需要运动消除
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。延迟时间:
ADC通常为2 oups,但在Haswell上特殊设置的立即数为 adc
仅为1 uop / 1c 。 adc eax,0
在Core 2上始终是2c延迟。进行此优化的第一个方法可能是SnB,但希望我们在Which Intel microarchitecture introduced the ADC reg,0 single-uop special case?上得到答案
test
清除CF而不管其值如何,但我认为(未测试)CF仍然依赖于源寄存器。如果没有,那么在Broadwell及更高版本上使用TEST / ADOX可能会有用。 (因为CF在大多数CPU上是单独重命名的,但是OF可能仅与ZF / SF属于同一捆绑包,并且其他标志确实取决于AND结果。)