CPU、操作系统和网络中的流水线

流水线在优化效率中特别常见,这里就对比一下这三块中用到的方法,把他们串起来

从最底层的CPU,再到操作系统,最后到网络一层层向上

CPU中的流水线

CPU中,一条指令的执行可以分为 取指、译码、执行、访存、写回和PC更新 6个阶段,当执行到某一阶段时,其他电路就会停下来等待,这样就造成了资源的浪费。因此采用流水线当一条指令还没执行完,就开始执行下一条指令。

  • 取指(Fetch):CPU根据 PC 寄存器从内存中取出指令字节。并从字节中识别指令类型,判断是否含有寄存器指示符、常数等,计算指令长度。

    指令类型由 icodeifun 两个值表示

  • 译码(Decode):从寄存器文件读取数据

  • 执行(Execute):ALU进行运算(有三种类型的运算:算术逻辑运算、有效地址的计算和 push,pop 指令中栈的计算)

  • 访存(Memory):内存的读写

  • 写回(Write back):将计算结果写回到寄存器中

  • PC更新:将PC设置为下一条指令的地址。当涉及到条件判断时,采用 分支预测 策略,先假定要执行其中一个。如果预测失败,在进行相应的回退

一条指令执行的 6 个部分不一定全部都执行

在第5个时刻,CPU的所有部件都运行起来了

引入流水线后,相邻指令之间就会出现一些问题,可以分为 2 类

  • 数据冒险:当一条指令的执行需要用到正在运行的指令的结果,如果不进行处理,就会产生错误
  • 控制冒险:当遇到 jmp,ret 或调用这些指令时,需要计算下一个指令的地址,可能会出现错误

数据冒险

解决数据冒险最简单的策略就是 暂停,在各个阶段的电路中运行一段空的指令(气泡)。气泡不会改变寄存器和CPU的状态

有些数据在写回阶段前就已经计算出了结果,因此为了提高效率,可以直接把执行阶段的计算结果 转发 给译码阶段

当然,有些指令到了后期才出结果,就算转发也会出现数据冒险(如 mov (%rbx), %rax ),这时候就要 转发 + 暂停 结合使用

控制冒险

当执行 ret 指令时,下一条指令的地址在内存栈中,因此要在访存阶段才会得到下一条指令的地址。再就是 jmp 指令不好预测

ret 指令,就是通过暂停来解决控制冒险

jmp 指令,当发现预测错误,就立即停止正在执行的错误代码,然后向后面的阶段 插入气泡 ,重新执行正确的代码

当然,执行的时候可能出现不止一个错误。采用暂停 + 插入气泡的方式解决

操作系统中的流水线

操作系统中的流水线主要就是CPU进程的切换。当进程因为 IO 或超时计时器到时了,操作系统就需要切换应用进程,保证CPU一直处于工作中。

和CPU中的流水线相比,操作系统麻烦的地方在于数据的保存。由于切换后需要恢复进程的上下文,同时,由于涉及到对操作系统核心的保护,操作系统的切换分成了 5 段

网络中的流水线

计算机网络中的流水线有两个地方

  • 应用层 HTTP1.1 中采用持续性链接的 HTTP,在同一个 TCP 连接中,一个请求的响应还没接收就继续发送下一个请求
  • 传输层中 TCP 连接发送报文段。设置发送缓冲区 > 1 以实现流水线发送,根据接收缓冲区的大小有两种流水方式,第一种是 GBN 协议,另外一种是 SR 协议

网络中的流水线不需要考虑到计算出错,也不需要考虑恢复状态,因此简单很多,只需要考虑差错恢复。

总结

由于不同的使用环境,导致流水线不同的关注重点。CPU要求执行不能出错,因此 CPU 流水线对相邻指令干扰的情况设置了暂停、气泡的方式;操作系统要求切换前后状态不变,因此操作系统流水线设置了 5 段以及映射表的切换,来实现切换前后进程的栈、PCB和内存是不变的;网络丢失了重发就可以,因此只需要考虑差错恢复,设置重发的规则即可。


CPU、操作系统和网络中的流水线
https://fu-qingchen.github.io/2021/06/10/HUST/PipelineInCPU&OS&Net/
作者
FU Qingchen
发布于
2021年6月10日
许可协议