单指令周期处理器

一个时钟周期内正好处理一条指令。然而时钟周期是固定的,但是指令的电路复杂程度是不同的,所以实际一条指令的执行时间是不同的。不同指令的执行时间不同,但是我们需要让所有指令都在一个时钟周期内完成,那就只好把时钟周期和执行时间最长的那个指令设成一样。6c85e2dd9b9988d8a458fb1200d96eee.webp
所以,在单指令周期处理器里面,无论是执行一条用不到 ALU 的无条件跳转指令,还是一条计算起来电路特别复杂的浮点数乘法运算,都等要等满一个时钟周期。虽然 CPI 能够保持在 1,但是时钟频率却太低。

时钟频率太高的话,有些复杂指令没有办法在一个时钟周期内运行完成。那么在下一个时钟周期到来,开始执行下一条指令的时候,前一条指令的执行结果可能还没有写入到寄存器里面。那下一条指令读取的数据就是不准确的,就会出现错误。

现代处理器的流水线设计

已知 CPU 运行的大致流程:取指令的时候,需要译码器把数据从内存里面取出来,写入到寄存器中;在指令译码的时候,需要另外一个译码器,把指令解析成对应的控制信号、内存地址和数据;指令执行的时候,需要是一个完成计算工作的 ALU。这些都是一个一个独立的组合逻辑电路,我们可以把它们看作一个团队里面的产品经理、后端工程师和客户端工程师,共同协作来完成任务。1e880fa8b1eab511583267e68f0541ad.webp
这样就不用把时钟周期设置成整条指令执行的时间,而是拆分成完成这样的一个一个小步骤需要的时间。同时,每一个阶段的电路在完成对应的任务之后,也不需要等待整个指令执行完成,而是可以直接执行下一条指令的对应阶段。这样的协作模式,就是我们所说的指令流水线。这里面每一个独立的步骤,我们就称之为流水线阶段或者流水线级(Pipeline Stage)。

如果我们把一个指令拆分成“取指令 - 指令译码 - 执行指令”这样三个部分,那这就是一个三级的流水线。如果我们进一步把“执行指令”拆分成“ALU 计算(指令执行)- 内存访问 - 数据写回”,那么它就会变成一个五级的流水线。

五级的流水线,就表示在同一个时钟周期里面,同时运行五条指令的不同阶段。这个时候,虽然执行一条指令的时钟周期变成了 5(见上图,第一行代表一条指令的完成过程,将该过程分为五个部分依次执行),但是可以把 CPU 的主频提得更高了。此时不再需要确保最复杂的那条指令在时钟周期里面执行完成,而只要保障一个最复杂的流水线级的操作,在一个时钟周期内完成就好了。

虽然不能通过流水线,来减少单条指令执行的“延时”这个性能指标,但是,同时在执行多条指令的不同阶段,提升了 CPU 的“吞吐率”。在外部看来,CPU 好像是“一心多用”,在同一时间,同时执行 5 条不同指令的不同阶段。在 CPU 内部,其实它就像生产线一样,不同分工的组件不断处理上游传递下来的内容,而不需要等待单件商品生产完成之后,再启动下一件商品的生产过程。

超长流水线的性能瓶颈

流水线中,用来同步时钟周期的,不再是指令级别的,而是流水线阶段级别的。每一级流水线对应的输出,都要放到流水线寄存器(Pipeline Register)里面,然后在下一个时钟周期,交给下一个流水线级去处理。所以,每增加一级的流水线,就要多一级写入到流水线寄存器的操作。虽然流水线寄存器非常快,比如只有 20 皮秒(ps,10−12 秒)。d9e141af3f2c5eedd5aed438388cfe26.webp 但是,如果我们不断加深流水线,这些操作占整个指令的执行时间的比例就会不断增加。最后,我们的性能瓶颈就会出现在这些 overhead 上。当流水线过长时,回导致 overhead 占比增大,这也就意味着,单纯地增加流水线级数,不仅不能提升性能,反而会有更多的 overhead 的开销。所以,设计合理的流水线级数也是现代 CPU 中非常重要的一点。

扩展阅读:Modern Microprocessors, A 90-Minute Guide!