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

5.9.      写转发暂停(Storeforwarding stalls)

访问一个内存操作数一部分的问题,比访问一个寄存器一部分的问题,要严重得多。对之前的处理器,这些问题都是相同的,参考第76页。

例子:

;Example 5.8a. Store forwarding stall

movdword ptr [mem1], eax

movdword ptr [mem1+4], 0

fild  qword ptr [mem1]                       ; Large penalty

通过改变为这样,你可以节省10-20个时钟周期:

;Example 5.8b. Avoid store forwarding stall

movdxmm0, eax

movqqword ptr [mem1], xmm0

fild    qword ptr [mem1]                    ; No penalty

5.10.      依赖链中的内存中介

P4有一个不幸的倾向,尝试在一个内存操作数就绪前读取它。如果你写

;Example 5.9. Memory intermediate in dependency chain

imuleax, 5

mov[mem1], eax

movebx, [mem1]

add ebx, ecx

在IMUL与内存写完成前,微处理器可能尝试将[MEM1]的值读到EBX。很快,它发现它读的这个值是无效的,因此它将丢弃EBX并再次尝试。它将持续重演这个读指令以及后续指令,直到[MEM1]中的数据就绪。看起来它可以重演指令序列多少次没有限制,这个过程盗取其他过程的资源。在一个长的依赖链里,代价通常是10-20个时钟周期!使用MFENCE指令串行化内存访问不能解决这个问题,因为这个指令代价更高。在其他微处理器上,包括P4E,在写入相同内存位置后,立即读这个内存操作数的代价仅是几个时钟周期。

当然在上面的例子中,避免这个问题最好的方式是以MOVEBX, EAX替换MOVEBX, [MEM1]。另一个可能的解决方案是,在同一个地址的写与读之间,给处理器大量的工作。

不过,有两个情形,不可能将数据保持在寄存器中。第一个情形是,在16位及32位模式中,在高级语言例程调用中的参数传递;第二个情形是在浮点寄存器与其他寄存器间传递数据。

传递参数到例程

在C++中以一个整形参数调用一个函数,在32位模式中看起来像这样:

;Example 5.10. Memory intermediate in function call (32-bit mode)

pusheax                                       ; Saveparameter on stack

call_ff                                           ;Call function _ff

addesp, 4                                    ; Clean up stack after call

...

_ffproc near                                ; Function entry

pushebp                                       ; Saveebp

movebp,esp                                ;Copy stack pointer

moveax,[ebp+8]                         ;Read parameter from stack

...

popebp                                         ; Restoreebp

ret                                                  ;Return from function

_ffendp

只要调用程序或被调用函数以高级语言写就,你可能必须坚持在栈上传递参数的惯例。当函数被声明为__fastcall时,大多数C++编译器可以在寄存器中传递2或3个整形参数。不过,这个方法不是标准的。不同的编译器使用不同的寄存器传递参数。为了避免这个问题,你可以汇编语言保持整个依赖链。在64位模式中这个问题可以被避免,其中大多数参数通过寄存器传递。

浮点与其他寄存器间的数据传递

没有办法在浮点寄存器与其他寄存器间传递数据,除了通过内存。例如:

;Example 5.11. Memory intermediate in integer to f.p. conversion

imuleax, ebx

mov[temp], eax             ; Transfer datafrom integer register to f.p.

fild[temp]

fsqrt

fistp[temp]                     ; Transfer data from f.p. register to integer

moveax, [temp]

这里,我们有通过内存传递数据两次的问题。你通过在浮点寄存器中保存整个依赖链,或使用XMM寄存器替代浮点寄存器来避免这个问题。

避免过早读内存操作数的另一个方式是使读地址依赖这个数据。第一个传递可以像这样做:

;Example 5.12. Avoid stall in integer to f.p. conversion

mov[temp], eax

and  eax, 0                ; Make eax = 0, but keepdependence

fild   [temp+eax]       ; Make read address depend on eax

AND EAX, 0指令将EAX设置为0,但保持在之前值上的一个虚假依赖。通过将EAX加入FILD指令的地址中,我们阻止了EAX就绪前的读。

在从浮点寄存器传输数据到整形寄存器时,制作类似的依赖性要复杂一点。解决这个问题最简单的方式是:

;Example 5.13. Avoid stall in f.p. to integer conversion

fistp    [temp]

fnstswax                                ;Transfer status after fistp to ax

and      eax, 0                         ; Set to 0

mov     eax,[temp+eax]       ; Make dependent on eax

文献

重演机制的一个详细研究由VictorKartunov等发表:“Replay:Unknown Features of the NetBurst Core”,www.xbitlabs.com/articles/cpu/print/replay.html。也可参考US Patents6,163,838; 6,094,717; 6,385,715。

5.11.      打破依赖链

将一个寄存器置零的一个常用方式是XOREAX, EAX或SUBEBX, EBX。P4/P4E处理器认识到这些指令与寄存器之前的值无关。因此,任何使用这个寄存器新值的指令将无需等待在XOR或SUB指令前面的值就绪。这同样适用于使用64位或128位寄存器的PXOR指令,但不适用于以下使用8位或16位寄存器的XOR或SUB:SBB,PANDN,PSUB,XORPS,XORPD,SUBPS,SUBPD,FSUB。

指令XOR,SUB与PXOR对打破不必要的依赖性是有用的,但它不能工作在比如PM处理器上。

你也可以使用这些指令来打破标记上的依赖性。例如在P4上,旋转(rotate)指令有对标记的一个虚假依赖。这可以下面的方式消除:

;Example 5.14. Break false dependence on flags

roreax, 1

subedx, edx       ; Remove false dependenceon the flags

rorebx, 1

如果你没有用于这个目的的一个空闲寄存器,那么使用一条不改变寄存器,仅改变标记的指令,比如CMP或TEST。你不能使用CLC来打破进位标记上的依赖。

相关文章
相关标签/搜索