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

4.3.      将复杂指令分解为更简单的指令

你可以分解读/修改以及读/修改/写指令来改进成对性。例如:

;Example 4.10a. P1/PMMX Imperfect pairing

add[mem1], eax

add[mem2], ebx

这个代码可以分解为一个序列,将时钟周期从5降到3:

;Example 4.10b. P1/PMMX Imperfect pairing avoided

movecx, [mem1]

movedx, [mem2]

addecx, eax

addedx, ebx

mov[mem1], ecx

mov[mem2], edx

类似的,你可以将不可成对指令分解为可成对指令:

;Example 4.11a. P1/PMMX Non-pairable instructions

push[mem1]

push[mem2] ; Non-pairable

分解为:

;Example 4.11b. Split nonpairable instructions into pairable ones

moveax, [mem1]

movebx, [mem2]

pusheax

pushebx ; Everything pairs

另一个非可成对指令可以分解为更简单、可成对指令的例子:

;Example 4.12. P1/PMMX Split non-pairable instructions

CDQsplit into: mov edx, eax / sar edx,31

noteax change to xor eax, -1

negeax split into xor eax, -1 / inc eax

movzxeax, byte ptr [mem] split into xor eax, eax / mov al, byte ptr [mem]

jecxzL1 split into test ecx, ecx / jz L1

loopL1 split into dec ecx, / jnz L1

xlatchange to mov al, [ebx+eax]

如果分解指令没有改进速度,你可以保留复杂的或不可成对指令,以减小代码大小。在更新的处理器上,不需要指令分解,除非指令分解产生更少的μops。

4.4.      前缀

带有一个或更多前缀的指令可能不能在V-管道中执行,它可能需要多个时钟周期来解码。

在P1上,每个前缀的解码时延是1时钟周期,除了条件近程跳转前缀0FH。

对0FH前缀,PMMX没有解码时延。段与重复前缀需要1个额外时钟周期来解码。地址与操作数大小前缀需要两个额外周期来解码。每时钟周期PMMX可以解码两条指令,如果第一条指令有一个段或重复前缀,或者没有前缀,第二条指令没有前缀。带有地址或操作数大小前缀的指令仅可以单独在PMMX上解码。带有多个前缀的指令,每个前缀需要1个额外时钟周期。

在前缀不可避免时,解码时延可能被掩盖,如果前接的指令需要多个时钟周期来执行。对P1来说,规则是:任何需要N时钟周期来执行(不是解码)的指令,可以掩蔽下两条(有时三条)指令或指令对的N-1个前缀的时延。换而言之,指令执行所需的每个额外时钟周期可用于解码一条后续指令中的一个前缀。这个掩蔽效果甚至扩展到一个被预测的分支。任何需要多个时钟周期来执行的指令,以及任何由于AGI暂停、缓存不命中、未对齐,或者其他原因,除了解码时延及分支误预测,而延迟的指令,都有这样一个掩蔽影响。

PMMX有一个类似的掩蔽效果,但机制不同。已解码指令被保存在一个透明的,可以保存最多4条指令的先进先出(FIFO)缓冲。当这个缓冲是空的时候,指令一解码就得到执行。如果指令的解码比执行快,缓冲被填充,即当你有不成对或多周期指令时。在指令执行比解码快时,FIFO缓冲被清空,即由于前缀导致的解码时延。在一个误预测分支之后,FIFO缓冲被清空。FIFO缓冲每周期可以接受2条指令,只要第二条指令没有前缀,且指令都不超过7个字节。两条执行流水线(U与V)每个每周期可以从FIFO缓冲接受一条指令。例如:

;Example 4.13. P1/PMMX Overshadow prefix decoding delay

cld

repmovsd

CLD指令需要2个时钟周期,因此可以掩蔽REP前缀的解码时延。如果CLD指令远离REPMOVSD,代码还需要多一个时钟周期。

;Example 4.14. P1 Overshadow prefix decoding delay

cmpdword ptr [ebx], 0

moveax, 0

setnzal

这里CMP指令需要2个时钟周期,因为它是一条读/修改指令。SETNZ指令的0FH前缀在CMP指令的第2个时钟周期期间解码,使得在P1上解码时延被隐藏(PMMX对0FH没有解码时延)。

4.5.      浮点代码调度

浮点指令不能以整形指令的方式成对,除了一个由下面规则定义的特殊情形:

·        第一条指令(执行在U-管道中)必须是FLD,FADD,FSUB,FMUL,FDIV,FCOM,FCHS或FABS。

·        第二条指令(在V-管道中)必须是FXCH。

·        跟在FXCH后的指令必须是一条浮点指令,否则FXCH将不完美成对,需要一个额外时钟周期。

这个特殊成对是重要的,下面很快会解释。

尽管通常浮点指令不能成对,许多可以被流水线化,即一条指令可以在前面指令完成之前开始。例如:

;Example 4.15. Pipelined floating point instructions

faddst(1), st(0) ; Clock cycle 1-3

faddst(2), st(0) ; Clock cycle 2-4

faddst(3), st(0) ; Clock cycle 3-5

faddst(4), st(0) ; Clock cycle 4-6

显然,如果第二条指令需要第一条指令的结果,这两条指令不能重叠。因为几乎所有的浮点指令都涉及栈寄存器顶,ST(0),看起来使得一条指令与前面指令的结果无关,可能性不大。这个问题的解决方案是寄存器重命名。FXCH指令实际上没有交换两个寄存器的内容:它仅交换了它们的名字。压入或弹出寄存器栈的指令也通过重命名工作。在Pentium上,浮点寄存器重命名已经被高度优化,使得一个寄存器可以在使用的同时被重命名。寄存器重命名从来不会导致暂停——在同一个时钟周期里甚至多次重命名一个寄存器也是可能的,比如当FLD或FCOMPP与FXCH成对时。

通过正确使用FXCH指令,在浮点代码中可以获得许多重叠。指令FADD,FSUB,FMUL及FILD各个版本都需要3个时钟周期且能够重叠,因此这些指令能被调度。如果内存操作数在1级缓存且正确地对齐,使用内存操作数不会比寄存器操作数的时间更多。

现在你必须习惯带有例外的规则,重叠规则也不例外:你不能在另一条FMUL后1周期开始一条FMUL指令,因为FMUL电路不是完全流水线化的。建议在两条FMUL之间放置其他指令。例如:

;Example 4.16a. Floating point code with stalls

fld[a1]      ; Clock cycle 1

fld[b1]      ; Clock cycle 2

fld[c1]       ; Clock cycle 3

fxchst(2)   ; Clock cycle 3

fmul[a2]   ; Clock cycle 4-6

fxchst(1)   ; Clock cycle 4

fmul[b2]   ; Clock cycle 5-7 (stall)

fxchst(2)   ; Clock cycle 5

fmul[c2]   ; Clock cycle 7-9 (stall)

fxchst(1)   ; Clock cycle 7

fstp[a3]     ; Clock cycle 8-9

fxchst(1)   ; Clock cycle 10 (unpaired)

fstp[b3]    ; Clock cycle 11-12

fstp[c3]     ; Clock cycle 13-14

这里,在FMUL[b2]及MUL[c2]之前有一个暂停,因为另一个FMUL在前接时钟周期里开始。你可以通过在FMUL之间放置FLD指令来改进这个代码:

;Example 4.16b. Floating point stalls filled with other instructions

fld[a1]        ; Clock cycle 1

fmul[a2]     ; Clock cycle 2-4

fld[b1]         ; Clock cycle 3

fmul[b2]     ; Clock cycle 4-6

fld[c1]         ; Clock cycle 5

fmul[c2]      ; Clock cycle 6-8

fxchst(2)     ; Clock cycle 6

fstp[a3]       ; Clock cycle 7-8

fstp[b3]       ; Clock cycle 9-10

fstp[c3]       ; Clock cycle 11-12

在其他情形里,你可以将FADD,FSUB或其他别的,放在FMUL之间来避免暂停。不是所有的浮点指令都可以重叠。某些浮点指令比后续指令相比,可以与更多后续指令重叠。例如,FDIV指令需要39个时钟周期。除了第一个时钟周期,其他都可与整形指令重叠,但仅最后两个时钟周期可与浮点指令重叠。一个完整的浮点指令列表,以及它们可以与谁成对或重叠,在手册4:“指令表”里给出。

在浮点指令上使用内存操作数没有惩罚,因为在流水线中,算术单元比读单元晚一个阶段。在将一个浮点值保存到内存时,出现这个折衷。带有一个内存操作数的FST或FSTP指令在执行阶段需要两个时钟周期,但它需要数据早一个时钟周期就绪,因此如果要保存的值没有在一个时钟周期前准备好,你将得到一个时钟的暂停。这类似于AGI暂停。在许多情形里,不把浮点代码调度为4个线程,或在中间插入一些整形指令,你不能掩蔽这种类型的暂停。FSP(P)指令执行阶段的这两个时钟周期不能与任何后续指令成对或重叠。

带有整型操作数的指令,比如FIADD,FISUB,FIMUL,FIDIV,FICOM,可以分解为更简单的操作,以促进重叠。例如:

;Example 4.17a. Floating point code with integer operands

fild[a]

fimul[b]

分解为:

;Example 4.17b. Overlapping integer operations

fild[a]

fild[b]

fmul

在这个例子中,通过重叠两条FILD指令,我们节省了两个时钟周期。

相关文章

相关标签/搜索