《See MIPS Run》–附录A 指令的时序和优化
作者 陈怀临 | 2008-06-07 18:14 | 类型 专题分析, 中国系统软件 | Comments Off
附录A 指令的时序和优化
A.1 避免冒险:使代码正确 可能的冒险包括下面这些情况: 3.协处理器0冒险:协处理器0控制指令通常用不同于平常的时序来读/写寄存器,这样就产生了流水线问题。其中多数没有互锁。详细信息必须从你的CPU用户手册中查,但是我们将看一下你在R4000 CPU(可能是MIPS CPU中最难处理的)上必须要做的事情。 注意分枝延迟指令槽,尽管它是为了降低流水化而被引入的,但它作为MIPS架构的一部分,因此不再是冒险了;它只是一个特例而已。 A.2 避免互锁来提高性能 只要CPU发生互锁,我们将会损失性能。但是如果用一些巧妙的法子,CPU本来是可以做些有用的事情的。我们想让编译器(or for heavily used function perhaps a dedicated human programmer)重新组织代码来得到最佳运行效果。 编译器—以及人—都发现这是一个挑战。一个高度优化已避免互锁的程序经常将运算的几个阶段分解,然后交错的执行,这样就很难看出将会发生什么。如果代码只是从原来的位置被前后移动了四,五条指令,通常还好处理。更大的移动就会出现越来越大的问题。 A.3 乘法单元冒险:早期修改lo and hi。 当一个MIPS CPU发生了中断或异常时,大部分流水线中的指令被中止,并且禁止将运算的结果写回。但是整数乘法单元很少与CPU的其余部分有关联,因此继续运行, 这并不会对异常产生影响。这意味着一旦乘法和除法指令开始以后,不能防止改变乘法单元的运算结果寄存器的lo and hi。 —————————————————————————————————- A.4 避免协处理器0冒险:有多少nop呢? 1.使用比标准时间(标准时间就是ALU阶段的末端)长的时间来产生数据的指令和/或 1. 列出运算结果迟了多少时钟周期的或者操作数早了多少时钟周期将是足够的—也是最简单的。但是MIPS系列的图表采用了流水线阶段。 为什么要减1呢?在第n+1流水时期产生的运算结果和一个在第n流水时期必需的操作数产生了理想的流水线,所以不需要nop。实际上减1是流水线运行阶段的人工模拟。 A.5 协处理器0指令/指令调度 我们在第6章看到了下面的在64位CPU(32位地址空间)上处理TLB扑空的一段代码: .set normorder TLBmissR4K: .set at 现在我们能够说明这里边的nops指令的数目了。 (1) R4000 CPU和它的大多数后代不能向下一条指令传递协处理器0寄存器值;dmfc0指令时序和load指令很象Heinrich的R4000/R4400用户手册暗示这个操作在R4000上可能会被完全互锁,并且可以肯定的是任何超过一个时钟周期的延迟都会互锁。但是它也没有变得简单一些,并且这里的nop对性能也不会产生任何不利的影响,所以我们把它保留在里边。 (2) 从表A.1,mtc0在流水线的第7步写EntryLo1寄存器,而tlbwr指令需要数据在流水线的第5步准备好。所以只需要一个nop(是这样计算的,7 – 5 – 1)。对于一些其它的CPU可能并不需要这个nop,但是为了可移植性的原因,还是值得保留下它的。 (3) tlbwr没有明显的相关性,但事实上非常重要的一点是在我们返回到用户态代码前,它所有的边际作用都会被完成。tlbwr只有到流水线的第8步才能完成写TLB,而正常指令的预取需要TLB在流水线的第二步准备好;我们必须在tlbwr和异常返回之间保留5个指令槽。eret后面跟着它的分枝延迟指令槽—在这种情况下在表中(5)处有一个nop—而且(由于R4000的长长的流水线的缘故)流水线会在分枝指令后面回填充一个”两时钟周期”的延迟。尽管如此,还是只有四条指令;所以我们需要在表中(3)处eret之前添加一个nop。 (4) 另外一个依赖性存在于eret之间,eret会将状态寄存器SR(EXL)域复位到它正常的用户态,并且是用户程序的第一个指令预取状态。但是,这个时序超出程序员的能力之外,所以机器已经内置了,分枝延迟时间槽加上”两时钟周期”如此之长的分枝延迟足够了。 有了这张表,你应该能够做任何事! A.6 协处理器0标志和指令 正如前面我们看到的,一些CPU控制寄存器(协处理器0)包含了位字段值或者标志,这些位字段值或者标志有其他指令运行而产生的副作用。一个常用的经验方法是假设在执行了一条mtc0指令后,三个指令周期内任何这样的副作用将是不可预知的, 1.启用/禁用一组协处理指令:如果你通过改变SR(CU)中的一位而启用了一个协处理器(使它特定的指令可用), mtc0指令在流水线第7步生效,因此新的运算值必须在协处理器指令的流水线第2步稳定下来。所以在这种情况下需要发射四个中间指令。 2.启用/禁用中断:如果你写SR(IE), SR(IM), 或者SR(EXL)而改变了CPU的中断状态,表A.1告诉我们在流水线的第7步开始生效。中断信号在指令流水线的第3步被采样,以决定是继续处理指令还是被中断所抢占。这意味着在新的中断状态被安全的安装之前,三条指令(是这样算出的:7 – 3 – 1)必须被执行。 在三条指令运行期间,中断能够被检测出来,并且能够引发异常。但是在被中断的指令前发射的指令改变了状态寄存器,所以规则告诉我们状态寄存器的改变还将会发生。 设想一下你已经通过设置异常等级位SR(EXL)禁用了中断。你通常只会在一个地方这样做,那就是在异常处理例程结束的地方。任何复杂情况的异常处理例程都保存异常开始处的SR值,当控制准备返回到用户态程序时再恢复这些值,这样异常开始处的SR(EXL) 的部分值就被设置了。 3.TLB改变和指令预取:在改变TLB和指令地址转换生效之间有五条指令的延迟。除此之外,有一个单条目缓冲器(single entry cache)被用于指令地址转换(称为微TLB),这种指令地址转换通过加载EntryHi而被隐式的覆盖掉;这也可能延迟生效的时间。 你只有在一个未映射的地址空间运行代码时必须显式的更新TLB。kseg0是通常的选择。 | |