通过寄存器重命名器对寄存器进行微体系结构归零:性能与mov?

ice*_*ine 12 x86 assembly x86-64 cpu-architecture

在一篇博客文章中读到,最近的X86微体系结构也能够在寄存器重命名器中处理常见的寄存器归零习语(例如将寄存器与自身对齐); 用作者的话来说:

"寄存器重命名器也知道如何执行这些指令 - 它可以将寄存器本身归零."

有人知道这在实践中是如何运作的吗?我知道有些ISA,如MIPS,包含一个在硬件中始终设置为零的架构寄存器; 这是否意味着在内部,X86微体系结构内部具有类似的"零"寄存器,以便在方便时映射到寄存器?或者我的心智模型对于这些东西如何在微体系结构上工作不太正确?

我之所以要问的原因是(从一些观察中)看来mov,在一个循环中,从一个包含零的寄存器到一个目的地,仍然比在循环内通过xor将寄存器归零要快得多.

基本上它发生的是我想根据条件将循环内的寄存器归零; 这可以通过提前分配架构寄存器来存储零(%xmm3在这种情况下),在整个循环期间不进行修改,并在其中执行以下内容来完成:

movapd  %xmm3, %xmm0
Run Code Online (Sandbox Code Playgroud)

或者用xor技巧代替:

xorpd   %xmm0, %xmm0
Run Code Online (Sandbox Code Playgroud)

(AT&T语法).

换句话说,选择是在循环之外提升常数零或在每次迭代中将其重新物化在其中.后者将实时架构寄存器的数量减少一个,并且通过处理器假设的特殊情况感知和处理xor成语,它似乎应该像前者一样快(特别是因为这些机器具有更多的物理无论如何,寄存器都比体系结构寄存器更重要,所以它应该能够在内部完成与我在程序集中所做的相同的工作,通过在内部提升常数零甚至更好,完全意识和控制自己的资源).但它似乎不是,所以我很好奇是否有任何具有CPU架构知识的人可以解释是否有一个很好的理论原因.

在这种情况下,寄存器由SSE寄存器发生,机器恰好是Ivy Bridge; 我不确定这些因素有多重要.

Ray*_*ger 13

执行摘要:xor ax, ax与较慢的mov immediate, reg指令相比,每个周期最多可以运行四条指令.

细节和参考:

维基百科对寄存器重命名有一个很好的概述:http://en.wikipedia.org/wiki/Register_renaming

TorbjëornGranlund针对AMD和Intel x86处理器的指令延迟和吞吐量的时序如下:http://gmplib.org/~tege/x86-timing.pdf

Agner Fog很好地涵盖了他的微架构研究中的细节:

8.8注册分配和重命名

寄存器重命名由寄存器别名表(RAT)和重排序缓冲器(ROB)控制......来自解码器和堆栈引擎的μop通过队列进入RAT,然后进入ROB读取和保留站.RAT每个时钟周期可以处理4μs.RAT可以在每个时钟周期重命名四个寄存器,甚至可以在一个时钟周期内将同一寄存器重命名四次.

独立的特殊情况

将寄存器设置为零的常用方法是将其与自身进行异或,或者将其从自身中减去,例如XOR EAX,EAX.如果两个操作数寄存器相同,则Sandy Bridge处理器识别出某些指令独立于寄存器的先前值.该寄存器在重命名阶段设置为零,而不使用任何执行单元.这适用于以下所有指令:XOR,SUB,PXOR,XORPS,XORPD,VXORPS,VXORPD以及PSUBxxx和PCMPGTxx的所有变体,但不适用于PANDN等.

不需要执行单元的说明

通过诸如XOR EAX,EAX之类的指令将寄存器设置为零的上述特殊情况在寄存器重命名/分配阶段处理,而不使用任何执行单元.这使得这些归零指令的使用非常有效,每个时钟周期具有四个归零指令的吞吐量.


Bru*_*son 6

归零中最大的性能成本隐藏在这句话中:

基本上它发生的是我想根据条件将一个寄存器归零

那句话意味着一个分支.即使分支被正确预测,它仍然可能花费多于归零寄存器.

至于寄存器重命名......

在OutOfOrder(OOO)CPU中,每次写入寄存器时,CPU都会为您提供一个新的寄存器.如果你执行了这三条指令:

xor eax,eax
add eax,eax
add eax,1
Run Code Online (Sandbox Code Playgroud)

然后对于第一条指令CPU(如果它是最近的英特尔CPU)只更新其映射,说eax现在指的是内部零寄存器.在第一次添加时,它从eax读取(两次,因为它被用作输入两次),然后更新其映射以指向新寄存器并将结果写入该寄存器.第二次添加也会发生同样的事情.因此,在这三个指令的过程中,eax寄存器被更改为指向三个不同的物理寄存器.

为什么?因为这:

mov eax,[esi]    ; Load from esi
add eax, 1
mov [esi], eax   ; Store to esi
mov eax,[esi+4]  ; Load from esi+4
add eax, 1
mov [esi+4], eax ; Store to esi+4
Run Code Online (Sandbox Code Playgroud)

在OOO处理器上,性能的主要限制之一是依赖性.必须按顺序执行一到三个指令.必须按顺序执行指令4到6.但是这两个块之间没有依赖关系.因此,一对三和四对六可以并行执行.但是,他们都提到了eax.

没问题.注册重命名解决了这个问题.第一和第四指令同时执行.CPU为指令流中的每个点创建一个单独的eax映射,后续指令对这些重命名的寄存器进行操作.这允许两个指令块完全并行执行.

由于各种原因,这实际上是非常复杂的,但它起作用,它是允许现代CPU运行如此之快的主要因素之一.

无论如何,长话短说,"xor eax,eax"甚至从未执行过,这很酷.这种优化可以应用于任何总是产生零或总是产生零的指令,或者其他什么,但英特尔只会在重要时花费晶体管来做这件事.我猜xorpd还没有削减.

我在博客上写了这篇文章(http://randomascii.wordpress.com/2012/12/29/the-surprising-subtleties-of-zeroing-a-register/),因为我认为这很酷.我也喜欢这样的想法:'add'和'sub',它们大多数是相同的指令,由于这种行为,可能会有轻微或极大的不同性能,尽管只是在从自身中减去寄存器的情况下.