CPU流水线设计
指令流水线: 若把指令执行拆分成取指令 -> 指令译码 -> 指令执行 三个部分、这就是一个三级的流水线、若把指令执行进一步拆分成 ALU
计算(指令执行) -> 内存访问 -> 数据写回 就变成了5级流水线、
1  | 五级流水线:  | 
超长流水线性能瓶颈
1  | 增加流水线的深度是有性能成本的  | 
流水线设计的冒险
流水线设计需要解决的三大冒险: 结构冒险, 数据冒险 和 控制冒险结构冒险: 本质上是硬件层面的资源竞争问题、CPU在同一个时钟周期、同时在运行两条计算机指令的不同阶段、但可能会用到同样的硬件了

1  | 如上所示: 在第一条指令执行到访存(MEM)阶段的时候、流水线里的第4条指令、在执行指令Fetch的操作、访存和取指令、都要进行内存数据的读取, 内存只有一个地址译码器的作为地址输入、就只能在一个时钟周期里读取一条数据、无法同时执行第一条指令的读取内存数据和第4条指令的读取指令的操作  | 
在CPU的结构冒险里、对于内存访问和取指令的冲突、直观的解决方案是把内存拆成2部分、让他们有各自的地址译码器、分别是存放指令的程序内存和存放数据的数据内存(对应体系结构叫 哈佛架构(Harvard Architecture)), 但这样拆对于指令和数据需要的内存空间、就无法根据实际的应用动态分配了、解决了资源冲突问题、也失去了灵活性
现代CPU架构借鉴了哈佛架构的思路、采用了普林斯顿架构、在高速缓存方面拆分成指令缓存和数据缓存
内存的访问速度远比CPU慢、所以现代的CPU不会直接读取主内存、会从主内存把指令和数据加载到高速缓存中、这样后续访问都是访问高速缓存、而指令缓存和数据缓存的拆分使得CPU在进行数据访问和取指令的时候、不会发生资源冲突了
数据冒险: 三种不同的依赖关系
1  | 数据冒险就是多个指令间有数据依赖的情况、可以分为 先读后写、先写后读和写后再写、如果满足不了依赖关系、最终结果就会出错  | 
通过流水线停顿(Pipeline Stall):插入nop操作 来解决数据冒险
操作数前推
1  | add $t0, $s2,$s1  | 

乱序执行
1  | 即使综合运用流水线停顿、操作数前推、增加资源等解决结构冒险和数据冒险的问题、仍然会遇到不得不停下整个流水线等待前一指令完成的情况、但是: 如果后边有指令不需要依赖前边指令的执行结果就可以不必等待前边指令完成、直接占用nop即可、这样的解决方案、在计算机里叫 乱序执行  | 



乱序执行的步骤
1  | 1. 取指令和指令译码阶段、同其它CPU、会一级一级顺序进行Fetch和Decode  | 
分支预测
缩短分支延迟: 将调解判断、地址调整 提前到指令译码阶段进行、无需放到指令执行阶段. 这种方式本质上和数据冒险的操作数前推方案类似、就是在硬件电路层面讲一些计算结果更早的反馈到流水线中、反馈更快、后边指令的等待时间就变短了
分支预测: 1) 静态预测、假装分支不发生. 分支预测失败的代价是: 丢弃已取出的指令&清空已使用的寄存器的操作
2) 动态分支预测:
a. 一级分支预测: 用1比特、记录当前分支的比较清空、来预测下一次分支时候的比较情况
b. 双模态预测器: 从2byte记录对应状态、提高预测的准确度
1  | 的流水线设计里、会遇到eg. 指令依赖等的情况、使得下一条指令不能正确执行. 但是通过抢跑的方式、可以得到提升指令 吞吐率 的机会、流水线架构的、是主动的冒险选择  | 
流水线设计总结
1  | 为了不浪费的性能、把指令执行的过程切分成一个个的流水线、来提升的吞吐率, 而每增加一级的流水线、也会增加overhead、同样因为指令不是顺序执行  |