单指令周期处理器

  1. 一条CPU指令的执行:Fetch -> Decode -> Execute
    • 这个执行过程,最少需要花费一个时钟周期,因为在取指令的时候,需要通过时钟周期的信号,来决定计数器的自增
  2. 单指令周期处理器(Single Cycle Processor):在一个时钟周期内,处理器正好能处理一条指令,即CPI1
  3. 时钟周期是固定的,但指令的电路复杂程度是不同的,因此一条指令的实际执行时间是不同的
    • 随着门电路层数的增加,由于门延迟的存在,位数多、计算复杂的指令需要的执行时间会更长
  4. 不同指令的执行时间不同,但需要让所有指令都在一个时钟周期内完成,只能把时钟周期和执行时间最长的指令设成一样
    • 快速执行完成的指令,需要等待一个时钟周期,才能执行下一条指令
  5. CPI能够保持在1,但时钟频率没办法设置太高,因为有些复杂指令是没办法在一个时钟周期内运行完成的
    • 在下一个时钟周期到来,开始执行下一条指令的时候,前一条指令的执行结果可能还没有写入到寄存器
    • 那么下一条指令读取的数据就是不准确的,会出现错误
  6. 无论是PC上使用的Intel CPU,还是手机上使用的ARM CPU,都不是单指令周期处理器,而是采用了指令流水线的技术

流水线设计

  1. 不用把时钟周期设置成整条指令执行的时间,而是拆分成完成一个小步骤需要的时间
  2. 每一阶段的电路在完成对应的任务之后,不需要等待整个指令执行完成,而是可以直接执行下一条指令的对应阶段
  3. 每一个独立的步骤,称之为流水线阶段或者流水线级(Pipeline Stage
  4. 如果把一个指令的执行过程拆分成:Fetch -> Decode -> Execute,那么这就是三级的流水线
    • 如果进一步把Execute拆分成:ALU计算(指令执行)-> 内存访问 -> 数据写回,那么就会变成一个五级的流水线
    • 五级的流水线,表示在同一个时钟周期内,同时运行五条指令的不同阶段
    • 虽然执行一条指令的时钟周期变成了5,但可以把CPU的主频提得更高
  5. 不需要确保最复杂的指令在时钟周期内执行完成,只要保证一个最复杂的流水线级的操作,能在一个时钟周期内完成
    • 如果某一个操作步骤的时间太长,可以将该步骤拆分成更多的步骤,让所有步骤需要执行的时间都尽量差不多
  6. 解决的问题
    • 单指令周期处理器的性能瓶颈来自于最复杂的指令
    • 不能通过流水线,来减少单条指令执行的延时;但通过同时执行多条指令的不同阶段,提升了CPU的吞吐率
  7. 现代的ARM CPU或者Intel CPU,流水线级已经到达了14级

性能瓶颈

  1. 增加流水线深度,是有性能成本
  2. 用来同步时钟周期的,不再是指令级别的,而是流水线阶段级别
  3. 每一级流水线对应的输出,都要放到流水线寄存器里面,然后在下一个时钟周期,交给下一个流水线级去处理
    • 每增加一级的流水线,就要多一级写入到流水线寄存器的操作(速度非常快,**$20ps = 20 \times 10^{-12}s$**)
    • 如果不断地加深流水线,开销就会越大,单纯地增加流水线级数,不仅不能提升性能,反而会有更多的overhead的开销

奔腾4

  1. 为了达到10GHz,在NetBurst架构中,使用了超长的流水线
    • Pentium III的流水线深度为11级,现在日常使用的手机ARM CPU或者Intel i7服务器的CPU,流水线的深度是14级
    • Pentium 4的流水线深度是20级,到了代号为Prescott的90纳米工艺处理器Pentium 4,流水线深度增加到了31级
  2. 同主频下,增加流水线深度,其实是降低了CPU的性能
    • 因为一个Pipeline Stage,就需要一个时钟周期
    • 31个Stage的3GHz主频的CPU,其实和11个Stage的1GHz主频的CPU,性能其实是差不多
      • 实际上,因为每个Stage都需要对应的Pipeline寄存器的开销,更深的流水线性能可能还会更差

响应时间 + 吞吐率

  1. 流水线技术不能缩短单条指令响应时间这个性能指标,但可以增加在运行很多条指令时候的吞吐率
    • 因为不同的指令,实际执行需要的时间是不同的
  2. 顺序执行3条指令
    • 一条整数的加法,需要200ps
    • 一条整数的乘法,需要300ps
    • 一条浮点数的乘法,需要600ps
  3. 在单指令周期CPU上运行,三条指令都需要600ps,总执行时间为1800ps
  4. 如果采用6级流水线CPU,每个Pipeline Stage都只需要100ps
    • 指令1的第一个100ps的Stage结束之后,指令2就开始执行
    • 指令2的第一个100ps的Stage结束之后,指令3就开始执行
    • 3条指令顺序执行所需要的总时间为800ps
    • 在1800ps内,使用流水线的CPU比单指令周期的CPU可以多执行一倍以上的指令数
    • 每条指令从开始到结束的时间并没有变化,即响应时间没有变化,但同样的时间内,完成的指令数增多了,即吞吐率上升

冒险 + 分支预测

  1. Intel CPU支持的指令集很大(2000+)
    • 有的指令很简单,如无条件跳转指令,不需要通过ALU进行任何计算,只需要更新一下PC寄存器里面的内容即可
    • 有的指令很复杂,如浮点数的运行,需要进行指数位比较、对齐,然后对有效位进行移位,然后再进行计算
    • 两者的执行时间可能相差二三十倍,那Pentium 4的超长流水线是不是很合理?
  2. Pentium 4失败的主要原因:功耗问题
    • 提升流水线深度,必须要和提升CPU主频同时进行,才能保持和原来相同的性能
    • 由于流水线深度的增加,需要的电路数也变多了,也就是所使用的晶体管也变多了
    • 功耗变大的原因:主频提升 + 晶体管数量增加

冒险

1
2
3
int a = 10 + 5; // 指令1
int b = a * 2; // 指令2
float c = b * 1.0f; // 指令3
  1. 指令2不能在指令1的第一个Stage结束后进行,因为指令2依赖指令1的计算结果,同样指令3也依赖指令2的计算结果
  2. 即使采用了流水线技术,这三条指令执行完成的时间为200 + 300 + 600 = 1100ps,而非前面的理论值800ps
    • 流水线技术带来的性能提升,只是一个理想情况
  3. 上面的依赖问题,就是计算机组成里面所说的冒险(Hazard)
    • 上面的例子是数据层面的依赖,即数据冒险
    • 在实际应用中,还会有结构冒险控制冒险等其它依赖问题
    • 解决方案:乱序执行分支预测
  4. 流水线越长,冒险的问题越难解决,因为同一时间同时运行的指令太多了
  5. 如果是20级流水线,需要确保这20条指令之间没有依赖关系
    • 而平时写程序,通常前后的代码都有一定的依赖关系
    • 所以超长流水线的执行效率反而下降

乱序执行

1
2
3
4
5
6
7
8
9
int a = 10 + 5; // 指令1
int b = a * 2; // 指令2
float c = b * 1.0f; // 指令3
int x = 10 + 5; // 指令4
int y = a * 2; // 指令5
float z = b * 1.0f; // 指令6
int o = 10 + 5; // 指令7
int p = a * 2; // 指令8
float q = b * 1.0f; // 指令9
  1. 先执行1、4、7三条指令,这三条指令之间是没有依赖关系的
  2. 再执行2、5、8以及3、6,9,这样又能够充分利用CPU的计算能力

小结

  1. 流水线技术和其它技术一样,都讲究折衷(Trade-Off)
  2. 一个合理的流水线深度,会提升CPU执行计算机指令的吞吐率
  3. 一般用IPC(Instruction Per Cycle)来衡量CPU执行指令的效率
    • IPC是CPI(Cycle Per Instruction)的倒数
  4. 过深的流水线,不仅不能提升计算机指令的吞吐率,还会加大计算的功耗散热问题
  5. 流水线技术带来的吞吐率提升,只是一个理想情况下理论值,实际应用中,还需要解决指令之间的依赖问题
    • 超长的流水线的执行效率变得很低

参考资料

深入浅出计算机组成原理