cpu运算可以简化成两大块,运算单元和寄存器。大致过程是先从寄存器中取值然后放入运算单元中运算完成之后又放入寄存器中。大致过程可分为六个部分

  1. fetch(取指),读入指令
  2. decode(译码),解码然后把寄存器中的值放到运算单元中
  3. execute(执行), 进行计算
  4. memory(访存),把结果放到内存中
  5. write back(写回),把结果放到寄存器中
  6. PC(更新PC)

这六个部分和把数据放到寄存器所用的总时间就是执行一条指令所需要的时间。cpu中有一个时钟,时钟以特定的周期进行高电压和低电压的变换。每一个周期内cpu执行一条指令,这个周期就是时钟周期。

但是这样速度不够快,因为cpu同一时间内只有一部分在工作,其他的都处于待机状态,所以可以用一种办法把其他部分利用起来。

之前我们之所以不能连续送入多条指令的原因是如果牵一条指令还未执行完成后一条指令便开始执行很可能导致电路出现问题(先这样理解吧)。如果我们在中间插入寄存器的话便不存在这个问题了。

例如:

| 算术单元 | 寄存器 |
| 300ps | 20ps |
| 算术单元1 | 寄存器1 | 算术单元2 | 寄存器2 | 算术单元3 | 寄存器
| 100ps | 20ps | 100ps | 20ps | 100ps | 20ps

乍一看加了寄存器之后时钟周期反而变长了,便成360ps。但实际上现在可以每120ps送入一条指令,先前的运算结果可以先储存在寄存器中,新一个时钟周期时把寄存器中的值送入下一个运算单元,这样就节省了时间

如果把时间分成3部分来运算
abcabcabc 不用流水线
abc
abc 用流水线
abc

可以看出使用流水线后时间明显缩短

但是流水线也有一些问题。

  1. 不能无限划分,随着划分的增多价格问题也越来越严重,性能提升却不大。甚至无限划分的时候,时间反而会变成无穷大(因为寄存器上时间过多,永远也执行不了指令)。
  2. 不一致的划分,因为流水线的时钟频率是根据耗时最长的那一阶段来确定的,所以如果划分不一致,就会导致最长的那段时间增大。正因如此,cpu一般把取指和PC这两个阶段当做一个阶段在最开始执行。
  3. 数据相关。 程序中上一条指令和下一条指令很可能有关联[例]
    [例]: movq rax rbx movq rbx rcx
  4. 控制相关。 主要是条件跳转指令,因为一次输入了多条指令,所以很可能前面的判断没有完成的时候条件判断语句后面的指令已经开始计算了,这时候如果条件跳转指令跳转到了另一个地方,那前面开始计算的部分全部要清空,然后重新载入流水线。这样就会极大的影响效率。

解决办法:

对于数据相关可以中间插入一个空命令例如

a阶段只有命令的传入,并没有涉及到数据的传入,假设第一条和第二条有冲突

a b c

a b c
a b c

但是这样还是会导致效率的降低。

一个更好的办法是在cpu内部对命令进行重排,但是又不影响运行逻辑。

对于控制相关可以使用数据传输指令,但是数据传输指令也具有局限性。

还可以通过分支预测的方法。如果是第一次进入分支,直接顺序传入指令,如果不是第一次,可以传入上一次分支运行时的指令。这样对循环具有优化作用。

此外在编译器层面,可以提前预测那种可能性比较大。然后把可能性大的部分放在分支语句的后面。