mov 消除的要点在于,不是分配一个新的 PRF 条目(物理寄存器文件)并运行 uop 来读取值并将其写入该新条目(例如lea rdx, [rcx+0]
会),mov rdx, rcx
可以通过让 RDX 的 RAT 条目(寄存器分配表)指向与 RCX 此时相同的物理寄存器号来处理。
因此,整个想法是改变 PRF 条目在某个时刻作为单个架构寄存器的状态的规则。这可能使得跟踪 PRF 条目何时可以被释放、或者当两个架构寄存器都引用相同的物理寄存器时重命名以后的微指令变得更加复杂,或者其他一些复杂情况。
“移动消除槽”是单独的资源,而不是 PRF 条目。它们的存在是为了解决英特尔遇到的任何额外的跟踪问题。当您覆盖目标时,移动消除槽会被释放。mov
稍后再说,例如mov ecx, edx
/ not ecx
立即释放所需的任何移动消除资源。
如果没有移动消除,你对它的工作原理是正确的;一个 PRF 条目保存仅写入一个架构寄存器的值,并且是在覆盖该寄存器之前读取该寄存器的任何微指令的输入依赖项。
除了 PRF 条目还有 FLAGS 条件代码的空间之外,因此在类似这样的指令之后add eax, ecx
写入 FLAGS 和整数寄存器,RFLAGS 和 RCX 都指向相同的物理寄存器。稍后的指令如mov
-即时,not
or lea
可以覆盖 gp 寄存器,只留下 CF 和 SPAZO 组 FLAGS 指向旧的物理寄存器。指令如cmp
, stc
, or add [mem], eax
写入(部分)FLAGS,但不写入整数寄存器。
但这只是两件事(FLAGS 的单独重命名部分,CF 和 SF/PF/AF/ZF/OF 又名 SPAZO https://stackoverflow.com/questions/49867597/what-is-a-partial-flag-stall),它可能仍然可以引用 phys 寄存器,而不是 GP 整数寄存器。每个 phys reg 可能有 1 位来跟踪它是否仍然被 GP-integer reg 引用,当退役写入 GP-integer 寄存器的 uop 时,退役可以正确释放它们,也许只需检查 RAT 条目的退役状态对于标志。或者,也许每个 PRF 条目都有 3 位,每个 GP 整数、CF 和 SPAZO,作为退出的一种方式,以确定何时可以释放物理寄存器(当它退出一个微指令,该微指令会覆盖最后一个架构引用)它。)
BeeOnRope 建议不要在每个 PRF 条目中进行完整的引用计数(计数器最多可以计数到 15,以防万一)mov ecx, eax
/ mov edx, eax
/ ...),移动消除槽实际上是引用计数。
异或归零总是可以被消除,因为物理零寄存器永远不需要被释放,所以它不需要被引用计数。 (整数和向量的物理零寄存器的存在是从 SnB 系列能够消除零微指令这一事实推断出来的。)
有关的:x86的MOV真的可以“免费”吗?为什么我根本无法重现这个? https://stackoverflow.com/questions/44169342/can-x86s-mov-really-be-free-why-cant-i-reproduce-this-at-all其中提到了英特尔优化手册中提到的一些内容,即希望尽快覆盖寄存器副本的结果,以提高 mov-elimination 的成功率。但至少当时Intel并没有提及涉及哪些CPU资源限制的细节。
Skylake 比 Ivy Bridge 有更多的 mov 消除槽,因为我的测试表明它不会在测试用例中遇到瓶颈,他们用来说明及时覆盖 mov 的好处。
非常不幸的是,英特尔搞砸了 Ice Lake / Tiger Lake,不得不通过微代码更新禁用其 mov-elimination(对于 GP-integer),因为覆盖了mov
立即通常意味着它是关键路径延迟的一部分,如果您的代码可能在没有 mov-elimination 的情况下在 CPU 上运行,则与您想要的相反。它在 Alder Lake 和 Rocket Lake 再次发挥作用。
在许多情况下,您很快就会覆盖副本和原始文件,因此可以在几条指令中保持目的地不变。理想情况下,避免长期不修改副本,除非这会花费更多的微指令或使 Ice Lake 上的关键路径延迟更糟。 (例如,如果您保存副本并且只读取它。)下一个中断通常会导致所有寄存器无论如何都被保存/恢复,因此即使对于具有一些长的代码,这也不是一个可以“建立”的问题运行带有许多 mov 消除副本的循环。