Intel, AMD及VIA CPU的微架构(14)

5.6.      执行单元间传输数据

一个操作的时延在大多数情形中变得更长,如果下一条依赖的指令没有在相同的执行单元里执行。例如(P4E):

;Example 5.5. P4E transfer data between execution units

                                             ;clock     ex.unit   subunit

paddwxmm0, xmm1       ; 0 - 2       MMX     ALU

psllwxmm0, 4                   ; 2 - 4       MMX    SHIFT

pmullwxmm0, xmm2      ; 5 - 12     FP          MUL

psubwxmm0, xmm3       ; 13 - 15   MMX     ALU

porxmm6, xmm7             ; 3 - 5       MMX    ALU

movdqaxmm1, xmm0    ; 16 - 23   MOV

pandxmm1, xmm4          ; 23 - 25   MMX     ALU

第一条指令PADDW运行在端口1下的MMX单元中,时延为2。偏转指令PSLLW运行在同一个执行单元里,虽然在另一个子单元中。没有额外的时延,因此它可以在时间T=2开始。乘法指令PMULLW运行在另一个执行单元,FP单元,因为在MMX执行单元中没有乘法子单元。这给出了一个额外时钟周期的时延。乘法直到T=5才开始,即使偏转指令在T=4完成。下一条指令PSUBW,回到MMX单元,因此再一次,从乘法结束到减法开始,我们有一个时钟周期的时延。POR不依赖前面任何指令,因此一旦端口1与MMX-ALU子单元都空闲,它可以开始。MOVDQA指令去往端口1下的MOV单元,这在PSUBW结束后给出了另一个1时钟周期的时延。最后的指令PAND去往端口1下的MMX单元。不过,在一条移动指令后没有额外的时延。这个序列需要25时钟周期。

在两个双倍速单元,ALU0与ALU1之间没有时延,但在P4上,从这些单元到其他(单倍速)执行单元有一个额外的半时钟周期的时延。例如(P4):

;Example 5.6a. P4 transfer data between execution units

                                  ; clock           ex.unit        subunit

andeax, 0fh            ; 0.0 - 0.5      ALU0           LOGIC

xorebx, 30h            ; 0.5 - 1.0      ALU0           LOGIC

addeax, 1                ; 0.5 - 1.0      ALU1           ADD

shleax, 3                  ; 2.0 - 6.0      INT             MMX SHIFT

subeax, ecx             ; 7.0 - 7.5      ALU0/1      ADD

movedx, eax           ; 7.5 - 8.0      ALU0/1      MOV

imuledx, 100          ; 9.0 - 23.0    INT               FP MUL

oredx, ebx              ; 23.0 - 23.5   ALU0/1      MOV

第一条指令AND在ALU0中在时间T=0开始。以双倍速运行,它在时间0.5结束。ALU0一空闲,XOR指令开始执行,在时间0.5。第三条指令ADD需要第一条指令的结果,但不需要第二条。因为ALU0被XOR占据,ADD只能去ALU1。从ALU0到ALU1没有时延,因此ADD可以在T=0.5开始,与XOR并行,在T=1.0结束。SHL指令运行在单倍速INT单元里。从ALU0或ALU1到其他单元有半时钟周期的时延,因此INT直到T=1.5才收到ADD的结果。以单倍速运行,INT单元不能在时钟周期的一半开始,因此它将等到T=2.0,在T=6.0结束。下一条指令SUB回到ALU0或ALU1。从SHL指令到其他执行单元有一个时钟周期的时延,因此SUB指令被推迟,直到T=7.0。在两条双倍速指令SUB与MOV之后,在IMUL在INT单元中运行之前,我们再次得到半个时钟周期的时延。IMUL,再次以单倍速运行,不能在T=8.5开始,因此它被推迟到T=9.0。在IMUL之后没有额外的时延,因此最后的指令可以在T=23.0开始,在T=23.5结束。

有几个方式改进这个代码。第一个改进是交换ADD或SHL的顺序(我们必须add(1 SHL 3) = 8):

;Example 5.6b. P4 transfer data between execution units

                                  ; clock              ex.unit             subunit

andeax, 00fh          ; 0.0 - 0.5        ALU0                LOGIC

xorebx, 0f0h           ; 0.5 - 1.0        ALU0                LOGIC

shleax, 3                  ; 1.0 - 5.0        INT                   MMX SHIFT

addeax, 8                ; 6.0 - 6.5        ALU1                ADD

subeax, ecx             ; 6.5 - 7.0        ALU0/1            ADD

movedx, eax           ; 7.0 - 7.5        ALU0/1            MOV

imuledx, 100          ; 8.0 - 22.0       INT                   FP MUL

oredx, ebx               ; 22.0 - 22.5     ALU0/1           MOV

这里,在SHL与IMUL之前,通过使这些指令的数据在半时钟滴答处就绪,因此对单倍速单元,在半个时钟周期后的整数时间可用,我们节省了半个时钟周期。该技巧是重排指令,使得在一个彼此依赖的指令串中,在任何两个单倍速μop之间,有奇数个双倍速μop。我们可以进一步改进这个代码,通过尽量减少执行单元间的转移次数。当然,更好的是将所有操作保持在同一个执行单元,优先选择双倍速单元。SHLEAX, 3可以被3× (ADD EAX,EAX)替代。

如果希望知道为什么在从一个执行单元到另一个时有额外的时延,有三个可能的解释:

解释A

在硅芯片上执行单元间的物理距离相当大,由于导线的电容、电感,可能导致电子信号从一个单元漫游到另一个的传播时延。

解释B

执行单元间的“逻辑距离”表示数据必须穿越各种寄存器、缓冲、端口、总线及多路复用器到达正确目的地。设计者实现了各种捷径来旁路这些时延元素,将结果直接转发(forward)给正在等待这些结果的执行单元。很可能这些捷径仅连接同一个端口下的执行单元。

解释C


在交错加法中,如果128位操作数一次处理64位,如图5.4提议那样,那么在一串128位指令末尾,在合并两个一半时,我们将有一个时钟周期的时延。例如,考虑在P4上128位寄存器中的封装双精度浮点值的加法。如果低64位操作数的加法在T=0开始,它将在T=4结束。高64位操作数可以在T=1开始,在T=5结束。如果下一个依赖操作也是一个封装加法,那么第二个加法可以在T=4开始在低64位操作数上工作,在高半部操作数就绪前。

图5.5

这样一串指令的时延将呈现我每个操作4个时钟周期。如果所有在128位寄存器上的操作可以这个方式重叠,那么我们将永远看不到128位操作有比对应的64位操作更高的时延。但如果到另一个执行单元的数据传输要求所有128位一起漫游,那么我们得到用于高低半部操作数同步的1时钟周期的额外时延,如图5.5所示。同样,在P4上的双倍速单元ALU0与ALU1把32位操作处理作两个每个需要半时钟周期的16位操作。但如果所有32比特需要在一起,那么有半个时钟周期的额外时延。未知执行单元间总线的宽度是32位,64位还是128位。

5.7.      回收

在P4与P4E中,已执行μop的回收与第六代处理器中那样工作。这个过程在第71页解释。

回收站每时钟周期可以处理3个μop。这看起来可能不是一个问题,因为在追踪缓存中吞吐率已经被限制为每时钟3个μop。但回收站有进一步的限制,被采用的跳转必须在回收站三个工位中的第一个里回收。这有时限制了小循环的吞吐率。如果在个循环中μop数目不是3的倍数,那么在循环底部的回跳指令可能去到错误的回收工位,代价是每次迭代一个时钟周期。因此,建议在小的关键循环中的μop数(不是指令)应该是3的倍数。在某些情形里,通过向循环添加一或两个NOP,使μop数被3整除,你确实可以每次迭代节省一个时钟周期。这仅适用于吞吐率预期每时钟周期3个μop的情形。。

5.8.      部分寄存器与部分标记

寄存器AL,AH与AX都是寄存器EAX的部分。这些称为部分寄存器。在第六代微处理器上,部分寄存器可以被分解为独立的临时寄存器,因此各部分可以被独立地处理。一旦需要将一个寄存器的不同部分合并为一整个寄存器,这将导致一个严重的时延。这个问题在第7187页解释。

P4/P4E以与PM不同的方式防止这个问题,即总是把整个寄存器保持在一起。不过,这个解决方案有其他缺点。第一个缺点是它引入了虚假的依赖。对AL的任何读写将被推迟,如果前面对AH的写被推迟。另一个缺点是访问部分寄存器有时要求一个额外的μop。

例如:

;Example 5.7. Partial register access

moveax, [mem32]      ; 1 uop

movax,   [mem16]      ; 2 uops

moval,    [mem8]        ; 2 uops

movah,   [mem8]        ; 2 uops

add al,    bl                   ; 1 uop

add ah,  bh                 ; 1 uop

add al,    bh                  ; 2 uops

add ah,   bl                   ; 2 uops

为了最优性能,在使用8位就16位操作数时,你可以遵循以下指引:

·        避免使用高8位寄存器AH,BH,CH,DH。

·        在读一个8位或16位内存操作数时,使用MOVZX读取整个32位寄存器,即使在16位模式里。

·        在需要符号扩展时使用带有最大可能目标寄存器的MOVSX,及在16或32位模式中32位目标寄存器,在64位模式中64位目标寄存器。

·        取而代之,如果可以封装,使用MMX或XMM寄存器来处理8位或16位整数。

当一条指令修改某些标记,但保留其他标记不变时,部分访问的问题也适用于标记寄存器。

出于历史原因,INC与DEC指令不改变进位标记,同时修改其他算术标记。这导致了对这些标记之前值的一个虚假依赖,代价是一个额外的μop。为了避免这些问题,建议总是使用ADD与SUB替代INC与DEC。例如,INCEAX应该被ADDEAX, 1代替。

SAHF不改变溢出标记,但修改其他算术标记。这导致了对这些标记之前值的一个虚假依赖,但没有额外的μop。

BSF与BSR改变零标记,但不改变其他标记。这导致了对这些标记之前值的一个虚假依赖,代价是一个额外的μop。

BT,BTC,BTR与BTS改变进位标记,但不改变其他标记。这导致了对这些标记之前值的一个虚假依赖,代价是一个额外的μop。使用TEST,AND,OR及XOR代替这些指令。在P4E上,你也可以有效地使用偏转指令。例如,BTRAX, 40 / JC X可以被SHR RAX, 41 / JC X替代,如果RAX的值在后面不再需要。

相关文章
相关标签/搜索