计算机组成 -- 指令流水线
单指令周期处理器
- 一条CPU指令的执行:Fetch -> Decode -> Execute
- 这个执行过程,最少需要花费一个时钟周期,因为在取指令的时候,需要通过时钟周期的信号,来决定计数器的自增
- 单指令周期处理器(Single Cycle Processor):在一个时钟周期内,处理器正好能处理一条指令,即CPI为1
- 时钟周期是固定的,但指令的电路复杂程度是不同的,因此一条指令的实际执行时间是不同的
- 随着门电路层数的增加,由于门延迟的存在,位数多、计算复杂的指令需要的执行时间会更长
- 不同指令的执行时间不同,但需要让所有指令都在一个时钟周期内完成,只能把时钟周期和执行时间最长的指令设成一样
- 快速执行完成的指令,需要等待满一个时钟周期,才能执行下一条指令
- CPI能够保持在1,但时钟频率没办法设置太高,因为有些复杂指令是没办法在一个时钟周期内运行完成的
- 在下一个时钟周期到来,开始执行下一条指令的时候,前一条指令的执行结果可能还没有写入到寄存器里
- 那么下一条指令读取的数据就是不准确的,会出现错误
- 无论是PC上使用的Intel CPU,还是手机上使用的ARM CPU,都不是单指令周期处理器,而是采用了指令流水线的技术
流水线设计
- 不用把时钟周期设置成整条指令执行的时间,而是拆分成完成一个小步骤需要的时间
- 每一阶段的电路在完成对应的任务之后,不需要等待整个指令执行完成,而是可以直接执行下一条指令的对应阶段
- 每一个独立的步骤,称之为流水线阶段或者流水线级(Pipeline Stage)
- 如果把一个指令的执行过程拆分成:Fetch -> Decode -> Execute,那么这就是三级的流水线
- 如果进一步把Execute拆分成:ALU计算(指令执行)-> 内存访问 -> 数据写回,那么就会变成一个五级的流水线
- 五级的流水线,表示在同一个时钟周期内,同时运行五条指令的不同阶段
- 虽然执行一条指令的时钟周期变成了5,但可以把CPU的主频提得更高
- 不需要确保最复杂的指令在时钟周期内执行完成,只要保证一个最复杂的流水线级的操作,能在一个时钟周期内完成
- 如果某一个操作步骤的时间太长,可以将该步骤拆分成更多的步骤,让所有步骤需要执行的时间都尽量差不多
- 解决的问题
- 单指令周期处理器的性能瓶颈来自于最复杂的指令
- 不能通过流水线,来减少单条指令执行的延时;但通过同时执行多条指令的不同阶段,提升了CPU的吞吐率
- 现代的ARM CPU或者Intel CPU,流水线级已经到达了14级
性能瓶颈
- 增加流水线深度,是有性能成本的
- 用来同步时钟周期的,不再是指令级别的,而是流水线阶段级别的
- 每一级流水线对应的输出,都要放到流水线寄存器里面,然后在下一个时钟周期,交给下一个流水线级去处理
- 每增加一级的流水线,就要多一级写入到流水线寄存器的操作(速度非常快,**$20ps = 20 \times 10^{-12}s$**)
- 如果不断地加深流水线,开销就会越大,单纯地增加流水线级数,不仅不能提升性能,反而会有更多的overhead的开销
奔腾4
- 为了达到10GHz,在NetBurst架构中,使用了超长的流水线
- Pentium III的流水线深度为11级,现在日常使用的手机ARM CPU或者Intel i7服务器的CPU,流水线的深度是14级
- Pentium 4的流水线深度是20级,到了代号为Prescott的90纳米工艺处理器Pentium 4,流水线深度增加到了31级
- 在同主频下,增加流水线深度,其实是降低了CPU的性能
- 因为一个Pipeline Stage,就需要一个时钟周期
- 31个Stage的3GHz主频的CPU,其实和11个Stage的1GHz主频的CPU,性能其实是差不多的
- 实际上,因为每个Stage都需要对应的Pipeline寄存器的开销,更深的流水线性能可能还会更差
响应时间 + 吞吐率
- 流水线技术不能缩短单条指令的响应时间这个性能指标,但可以增加在运行很多条指令时候的吞吐率
- 因为不同的指令,实际执行需要的时间是不同的
- 顺序执行3条指令
- 一条整数的加法,需要200ps
- 一条整数的乘法,需要300ps
- 一条浮点数的乘法,需要600ps
- 在单指令周期CPU上运行,三条指令都需要600ps,总执行时间为1800ps
- 如果采用6级流水线CPU,每个Pipeline Stage都只需要100ps
- 指令1的第一个100ps的Stage结束之后,指令2就开始执行
- 指令2的第一个100ps的Stage结束之后,指令3就开始执行
- 3条指令顺序执行所需要的总时间为800ps
- 在1800ps内,使用流水线的CPU比单指令周期的CPU可以多执行一倍以上的指令数
- 每条指令从开始到结束的时间并没有变化,即响应时间没有变化,但同样的时间内,完成的指令数增多了,即吞吐率上升了
冒险 + 分支预测
- Intel CPU支持的指令集很大(2000+)
- 有的指令很简单,如无条件跳转指令,不需要通过ALU进行任何计算,只需要更新一下PC寄存器里面的内容即可
- 有的指令很复杂,如浮点数的运行,需要进行指数位比较、对齐,然后对有效位进行移位,然后再进行计算
- 两者的执行时间可能相差二三十倍,那Pentium 4的超长流水线是不是很合理?
- Pentium 4失败的主要原因:功耗问题
- 提升流水线深度,必须要和提升CPU主频同时进行,才能保持和原来相同的性能
- 由于流水线深度的增加,需要的电路数也变多了,也就是所使用的晶体管也变多了
- 功耗变大的原因:主频提升 + 晶体管数量增加
冒险
1 | int a = 10 + 5; // 指令1 |
- 指令2不能在指令1的第一个Stage结束后进行,因为指令2依赖指令1的计算结果,同样指令3也依赖指令2的计算结果
- 即使采用了流水线技术,这三条指令执行完成的时间为
200 + 300 + 600 = 1100ps
,而非前面的理论值800ps- 流水线技术带来的性能提升,只是一个理想情况
- 上面的依赖问题,就是计算机组成里面所说的冒险(Hazard)
- 上面的例子是数据层面的依赖,即数据冒险
- 在实际应用中,还会有结构冒险、控制冒险等其它依赖问题
- 解决方案:乱序执行、分支预测
- 流水线越长,冒险的问题越难解决,因为同一时间同时运行的指令太多了
- 如果是20级流水线,需要确保这20条指令之间没有依赖关系
- 而平时写程序,通常前后的代码都有一定的依赖关系
- 所以超长流水线的执行效率反而下降了
乱序执行
1 | int a = 10 + 5; // 指令1 |
- 先执行1、4、7三条指令,这三条指令之间是没有依赖关系的
- 再执行2、5、8以及3、6,9,这样又能够充分利用CPU的计算能力
小结
- 流水线技术和其它技术一样,都讲究折衷(Trade-Off)
- 一个合理的流水线深度,会提升CPU执行计算机指令的吞吐率
- 一般用IPC(Instruction Per Cycle)来衡量CPU执行指令的效率
- IPC是CPI(Cycle Per Instruction)的倒数
- 过深的流水线,不仅不能提升计算机指令的吞吐率,还会加大计算的功耗和散热问题
- 流水线技术带来的吞吐率提升,只是一个理想情况下理论值,实际应用中,还需要解决指令之间的依赖问题
- 超长的流水线的执行效率变得很低
参考资料
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.