当前位置: 首页 > 知识库问答 >
问题:

使寄存器依赖于另一个寄存器而不改变其值

牛嘉谊
2023-03-14

考虑以下x86程序集:

; something that sets rax
mov rcx, [rdi]
xor rax, rcx
xor rax, rcx

序列结束时,rax的值与输入时的值相同,但从CPU的角度来看,其值取决于从内存加载到rcx的值。特别是,在该加载和两个异或指令完成之前,不会开始后续使用rax。

有什么方法可以比两个异或序列更有效地实现这种效果,例如,使用单个单uop单周期延迟指令?如果某个常量值需要在序列之前设置一次(例如,有一个零寄存器),则可以。

共有1个答案

端木阳荣
2023-03-14

目标寄存器的关键路径上只有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上,with zero并不是dep中断零的特例。

前端UOP:3(或BMI1的2)。延迟:

  • 从rcx到rax:2c(mov消除或BMI1)

对于零寄存器,如果可以将所有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

实际上,它只需要返回给定输入的已知值。如果其杂质仅限于具有其他副作用/输出,则这也有效。

不是在得到结果后进行两次异或运算,而是进行设置,这样我们就已经有了一个异或运算,我们只需再进行一次异或运算即可解读。或者使用加法,因为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/shld的1c延迟/0.5c吞吐量(类似于单寄存器移位),但我测试了自己,它肯定是3c延迟/1c吞吐量。在他们的github页面上报告为instlatx64错误。

SHLD对于复制一个依赖于另一个寄存器的32位寄存器也很有趣。e、 g.@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个UOP。Intel P6系列上有3个UOP,可能还有SnB/IvB。延迟:

  • 从rcx到rax:2c在HSW和更高版本上,3带有2-uop adc
  • 从rax到rax:HSW上的1c和更高版本,2和2-uop adc

Haswell及更早版本上的ADC通常为2 uops,但立即为0的adc在Haswell上的特殊情况仅为1 uop/1c。adc eax,0始终是Core 2上的2c延迟。第一个进行此优化的人可能是SnB,但希望我们能得到关于哪个英特尔微架构引入了ADC reg,0单uop特例的答案?

test无论值如何都会清除CF,但我认为(未经测试)CF仍然对源寄存器有依赖关系。如果没有,那么也许使用TEST/ADOX在Broadwell及以后会很有用。(因为CF在大多数CPU上都单独重命名,但OF可能只与ZF/SF和其他确实依赖于AND结果的标志属于同一捆绑包的一部分。)

 类似资料:
  • 在x86-64中,如果某些通用寄存器比其他寄存器更受欢迎,某些指令会执行得更快吗? 例如,会比执行得更快吗?我可以想象后者需要一个REX前缀,这会使指令获取速度变慢? 使用代替怎么样?或呢?其他操作?更小的寄存器,如vs?vs? AMD vs Intel?更新的处理器?较旧的处理器?指令的组合? 澄清:某些通用登记册是否应该优先于其他登记册,它们是哪些?

  • Vim提供了许多寄存器。可以将这些寄存器用作多个剪贴板。使用多个文件时,此功能非常有用。在本章中,将讨论以下主题内容 - 复制寄存器中的文本 粘贴寄存器中的文本 列出可用的寄存器 寄存器类型 1. 复制寄存器中的文本 对于复制,可以使用普通的命令,即并将其存储在寄存器中,可以使用以下语法 - 例如,要复制寄存器中的文本,请使用以下命令 - 2. 粘贴寄存器中的文本 从寄存器粘贴文本 - 例如,下面

  • 当前的Perl 5虚拟机是一台堆栈机器。 它通过将操作保持在堆栈上来传递操作之间的值。 操作将值加载到堆栈上,执行他们需要执行的操作并将结果放回堆栈。 这很容易使用,但速度很慢。 要将两个数字相加,您需要执行三次堆栈推送和两次堆栈弹出。 更糟糕的是,堆栈必须在运行时增长,这意味着在您不想分配内存时分配内存。 因此,Parrot将打破虚拟机的既定传统,并使用寄存器架构,更类似于真实硬件CPU的架构。

  • 处理器操作主要涉及处理数据。 该数据可以存储在存储器中并从其上访问。 然而,从存储器读取数据和将数据存储到存储器中会降低处理器的速度,因为它涉及通过控制总线向存储器存储单元发送数据请求并通过相同通道获取数据的复杂过程。 为了加速处理器操作,处理器包括一些内部存储器存储位置,称为registers 。 寄存器存储数据元素以便处理而无需访问存储器。 处理器芯片内置有限数量的寄存器。 处理器寄存器 IA

  • 80386中应用程序员感兴趣的有16个寄存器。如图2-5所示,这些寄存器被分成以下几个基本类型: 1.通用寄存器。这些32为通用寄存器主要用来数学和逻辑运算。 2.段寄存器。这些特殊目的寄存器允许系统软件设计者选择平坦模式或是段模式。这六个寄存器决定了,任何时候,哪段存储器可以被寻址。 3.状态和指令寄存器。这些特殊目的寄存器用于记录和改变80386处理器状态的一些特征。 2.3.1 通用寄存器

  • #include <stdio.h> int main(void) { int a =0; a++; a++; printf("%d\n", a); return 0; } 技巧 PC寄存器会存储程序下一条要执行的指令,通过修改这个寄存器的值,