14. 指令解码(PPro,PII 和 PIII)
在此我先讲指令解码,然后再讲取指令。 因为要理解取指令时发生的延迟,你必须先知道解码器的工作原理。
只有在一些条件满足的情况下,解码器才能在一个时钟周期内解码3条指令。
解码器D0能够处理所有的在一个时钟周期内最多产生4条微码的指令。 解码器D1和D2只能处理那些只产生1条微码的指令,而且那些指令长度不能超过8字节。
概述同一个时钟周期内解码2或3条指令的规则如下:
- 第一条指令(由D0解码)产生的微码不能超过4条
- 第二、三两条指令都只能产生1条微码
- 第二、三两条指令长度都不能超过8个字节
- 这些指令都要在同一个16字节的ifetch块中(见下一章)
在D0中的指令长度没有限制(尽管Intel手册中提到了一些),只要这三条指令能放入一个16字节的ifetch块。
产生4条以上微码的指令需要2个或更多时钟周期来解码,并且在这个过程中没有其它的指令可以并行解码。
根据以上规则,我们得出结论:一个时钟周期内解码器至多产生6条微码(如果第一条指令产生4条微码,后两条指令各产生1条微码);至少产生2条微码(如果所有指令都产生2条微码,这时D1和D2没法用)。
为了达到最大吞吐量,推荐你把代码组织成4-1-1模式:产生2-4条微码的指令可以"免费"附带2条产生1条微码的简单指令,某种意义上不增加解码时间,比如:
MOV EBX, [MEM1] ; 1条微码 (D0) INC EBX ;1条微码 (D1) ADD EAX, [MEM2] ; 2条微码 (D0) ADD [MEM3], EAX ; 4条微码 (D0)
解码要花去3个时钟。 重组代码使它们进入两个解码组可以节省一个周期:
ADD EAX, [MEM2] ; 2条微码 (D0) MOV EBX, [MEM1] ; 1条微码 (D1) INC EBX ; 1条微码 (D2) ADD [MEM3], EAX ; 4条微码 (D0)
现在解码器在2个时钟周期内产生8条微码,应该比较满意了。因为流水线的后续阶段只能在一个时钟周期内处理3条微码,所以大于3条/周期的解码吞吐率你就可以认为解码不是瓶颈了。然而,就像后面的章节描述的那样,取指令机制的复杂性可能会使解码延迟,因此安全起见,你的目标是每个时钟周期的解码吞吐率大于3。
你可以在29章的列表中查出各种指令产生的微码数。
在解码时,前缀也可能带来惩罚。 指令能够有这样一些前缀:
操作数尺寸前缀
当你在32位环境中有一个16位操作数时将用到,反之亦然(除了那些操作数只能有一种尺寸的指令,比如FNSTSW AX)。
当指令有一个16或32位的立即操作数时,操作数尺寸前缀会带来几个周期的惩罚,因为操作数的长度被前缀改变了。 比如:
ADD BX, 9 ;
因为立即操作数是8位,故没有惩罚
MOV WORD PTR [MEM16], 9 ; 因为操作数是16位,有惩罚
后一条指令应该被替换成:
MOV EAX, 9 MOV WORD PTR [MEM16], AX; 没惩罚,因为没有立即数
地址尺寸前缀
当你在16位模式下用32位地址时用到,反之亦然。它很少用到,一般应该避免。每当你有一个显式的内存操作数时(甚至有时没有偏移量),地址尺寸前缀导致一次惩罚。因为指令编码中指明r/m的位被前缀改变了。
只有隐式内存操作数的指令,比如串操作指令,即使有了地址尺寸前缀也没有惩罚。
段前缀
当你需要定位非默认的数据段时需要用到。 在 PPro,PII 和 PIII 上没有因段前缀而带来的惩罚。
重复前缀和锁前缀在解码时没有惩罚
当你的前缀多于一个时总是有惩罚
一般惩罚是每个前缀一个周期。