20. 依赖环(PPro,PII和PIII)
一组指令,其中每一条指令依赖于前一条指令的结果,称这组指令是一个依赖环。
长的依赖环应该尽可能避免,因为它阻止了乱序执行和并行执行。
比如:
MOV EAX, [MEM1]
ADD EAX,[MEM2]
ADD EAX, [MEM3]
ADD EAX, [MEM4]
MOV [MEM5],EAX
在这个例子中,每条ADD指令产生2条微码,一条从内存中读(端口2),一条做加法(端口0或1)。
读内存的微码可以乱序执行,但各条加法微码都必须等待前面的加法微码完成。 这个依赖环执行的时间并不长,因为每个加法只需要一个时钟。
但如果你有诸如乘法这样的慢指令,甚至更坏的除法形成依赖环,那么明显你应该做一些事情来打破依赖环。 可以用多个累加器实现这个目的:
MOV EAX, [MEM1] ; 开始第一个环
MOV EBX, [MEM2] ; 用另一个累加器开始第二个环
IMUL EAX, [MEM3]
IMUL EBX,[MEM4]
IMUL EAX, EBX ; 最后结合两个环
MOV [MEM5], EAX
这里,第二个乘法指令可以在第一个结束之前开始。
因为乘法指令花费4个周期且是完全流水化的,所以你最多可设4个累加器。
除法是非流水化的,因此你不能为除法依赖环做类似的事情。当然,你可以先将所有的除数相乘,最后再做一个除法。
浮点指令的延迟比整型指令大,因此你应该明确地打破长的浮点依赖环:
FLD [MEM1] ; 开始第一个环
FLD [MEM2] ; 用另一个累加器开始第二个环
FADD [MEM3]
FXCH
FADD [MEM4]
FXCH
FADD [MEM5]
FADD ; 最后把环结合
FSTP [MEM6]
你需要很多FXCH指令,不用担心,它们很“便宜”。
尽管在RAT,ROB和引退站中FXCH指令也被计为1条微码,但在RAT中FXCH指令通过寄存器重命名被“化解”,从而不会给执行端口造成任何负载。
如果依赖环很长,你需要设3个累加器:
FLD [MEM1] ; 开始第一个环
FLD [MEM2] ; 开始第二个环
FLD [MEM3] ; 开始第三个环
FADD [MEM4] ; 第三个环
FXCH ST(1) FADD [MEM5] ; 第二个环
FXCH ST(2) FADD [MEM6] ; 第一个环
FXCH ST(1) FADD [MEM7] ; 第三个环
FXCH ST(2) FADD [MEM8] ; 第二个环
FXCH ST(1) FADD ; 结合1、3环
FADD ; 与第2个环结合
FSTP [MEM9]
不要把中间结果存入内存后马上读出:
MOV [TEMP], EAX
MOV EBX, [TEMP]
试图在前面的写内存操作完成之前从相同地址读会带来惩罚。 就像上面的例子那样。可以把最后一条指令改成MOV
EBX,EAX或在两条指令之间插入一些其它指令。
有一种情形使你不可避免地要把中间结果存入内存,即从一个整型寄存器传输数据到一个浮点寄存器,反之亦然。 比如:
MOV EAX, [MEM1]
ADD EAX, [MEM2]
MOV [TEMP],EAX
FILD [TEMP]
如果在TEMP的写操作和TEMP的读操作之间你没有其它指令可插入,那么你可以考虑用浮点寄存器取代EAX:
FILD [MEM1]
FIADD [MEM2]
连续的跳转、调用和返回也可以看作是依赖环。 对于这些指令,吞吐量是每2个时钟周期1个转移指令。
因此推荐你在这些转移指令中间给处理器一些其它的事情做。