# 什么是流水线?

流水线是一种通过将指令的执行过程分解为多个子过程,并让这些子过程同时执行,从而实现并行加速的技术。这使得 CPU 的每条指令在执行过程中可以与其他指令的不同子过程同时进行,显著提高了指令的吞吐率。

通过流水线技术,可以将 CPI (每条指令的平均时钟周期数) 降低到接近 1,并将时钟周期降低到单周期处理器的 1/5,从而达到时空复用并行加速的目的。

CPU 执行时间的计算公式为:

CPU 执行时间=指令数×CPI×时钟周期CPU\ 执行时间 = 指令数 × CPI × 时钟周期

通过优化这个公式中的各个参数,可以提升 CPU 的整体性能。


# CPU 性能优化方法

除了流水线,还有多种技术可以用来优化 CPU 的执行时间,它们主要分为:指令级并行线程级并行异构计算

# 指令级并行

# 多周期

通过多周期设计,可以降低 CPI 和时钟周期的乘积,但效率不如流水线。

# 多发射

多发射技术使得 CPU 可以在每个时钟周期内取址并执行多条指令,从而将 CPI 降低到小于 1。它主要有两种实现方式:

  • 超标量 (Superscalar):这是一种动态指令调度技术。处理器设置多个独立的功能部件,并通过硬件动态调度,确保在同一时钟周期内执行多条指令。
  • 超长指令字 (VLIW):这是一种静态多发射技术。在一条指令中设置多个独立的字段,每个字段可以分别独立地控制各个功能部件并行工作,指令的并行性由编译器在编译时决定。

# 超级流水线 (Superpipelining)

通过增加流水线深度,进一步缩短每个流水线阶段的时钟周期,从而降低整体的时钟周期。

# 线程级并行

# 超线程 (Hyper-Threading)

当一个线程因为等待访存等原因被阻塞时,处理器可以执行另一个线程的任务。这是一种利用 CPU 空闲时间提高利用率的有效策略,可以采用贪心调度策略来优化任务分配。

# 多核 (Multi-Core)

将多个独立的处理器核心集成在一个芯片上,实现真正的并行处理。在多核系统中,并行加速的性能提升遵循阿姆达尔定律,即随着核数的增加,边际收益会逐渐降低。

  • 并行加速比 (SS) 的计算公式为:

    S=1/(1a+a/n)S = 1/(1 - a + a/n)

    其中,aa 为程序中可并行部分的比例,nn 为并行部分的加速比(通常可以理解为核数)。

在多核系统中,核心之间的数据共享和同步是需要重点考虑的问题。

# 异构计算

异构计算的核心思想是根据计算任务的特性,将其分配到最适合的硬件上执行,从而最大化系统性能。

  • GPU (图形处理器):GPU 是一种专门为大规模并行计算设计的硬件,其架构采用 SIMD (单指令流多数据流) 的设计理念,非常适合处理大规模、重复的并行任务。因此,在异构计算中,串行复杂的任务通常由 CPU 处理,而并行简单的任务则由 GPU 处理
  • TPU (张量处理器):TPU 是面向特定领域(如机器学习)的专用处理器。

# 流水线的基本概念

在深入理解流水线的性能之前,需要掌握几个基本概念:

  • 延时与吞吐率:延时指的是完成一条指令所需要的总时间,而吞吐率指的是单位时间内完成的指令数。它们通常用周期数时钟周期来衡量。
  • 调度:指的是将操作分配到特定的时钟周期中执行。
  • 资源绑定:指的是将操作分配到特定的硬件资源上执行,例如寄存器、运算单元等。

资源共享方式主要有两种:

  • 时分复用:在不同时间段内重复使用同一个硬件资源。
  • 空分复用:在同一时间段内使用多个硬件资源。

# 流水线的性能指标

对于一个包含 nn 条指令、kk 级流水线、级间延时为 Δt\Delta t 的系统,其性能指标可以从以下几个方面来衡量:

  • 实际吞吐率 (TPTP):

    TP=n/Tk=n/(k+n1)ΔtTP = n / T_k = n / (k + n - 1)\Delta t

  • 最大吞吐率 (TPmaxTP_{max} ):当指令数量趋近于无穷时,流水线的最大吞吐率。

    TPmax=1/ΔtTP_{max} = 1/\Delta t

  • 加速比 (SS):不使用流水线所用的时间与使用流水线所用的时间的比值。

    S=T0/TkS = T_0 / T_k

  • 实际加速比 (SS):

    S=nkΔt/(k+n1)Δt=kn/(k+n1)S = nk\Delta t / (k + n - 1)\Delta t = kn / (k + n - 1)

  • 最大加速比 (SmaxS_{max} ):当指令数量趋近于无穷时,流水线的最大加速比。

    Smax=kS_{max} = k

# 为什么无法达到最大加速比?

尽管理论上的最大加速比为 kk,但在实际应用中,很难完全实现,主要有以下几个原因:

  1. 启动开销:各级流水线需要依次启动,无法直接达到 kk 倍的加速效果。
  2. 流水线级长不均:流水线各级所需的工作时间可能不同,最终的时钟周期由最长的那一级决定,即 T=tmaxT=t_{max}
  3. 寄存器延时:流水线级间寄存器会引入额外的延时。

# 流水线中的“冒险”

“冒险”指的是由于指令之间存在依赖关系,导致流水线无法正常工作的情况,主要分为三种类型:

# 结构冒险 (Structural Hazards)

当两条指令需要同时使用同一个硬件资源时,就会发生结构冒险。

  • 解决方法
    • 增加硬件资源,如增加独立的存储器端口。
    • 调整指令占用资源的时间,例如将寄存器读写、RF 总线、存储器、ALU 等功能部件分离。

# 数据关联冒险 (Data Hazards)

当一条指令的执行依赖于流水线中正在运行的其他指令的结果时,就会发生数据关联冒险。

  • 解决方法
    • Stall:暂停流水线,等待依赖的数据就绪。
    • Forwarding:将前一条指令的执行结果直接通过旁路(Bypass)传递给后一条指令,而无需等待其写回寄存器。
    • 编译器优化:通过重新排列指令来消除或减少数据依赖。

# 控制冒险 (Control Hazards)

当取指令的地址 (PC) 依赖于流水线中某个分支指令的执行结果时,就会发生控制冒险。

  • 解决方法
    • Stall:暂停流水线,等待分支结果确定。
    • Forwarding:将分支结果提前传递给取指单元。
    • 延迟槽 (Delayed Slot):在分支指令后插入一条不受分支结果影响的指令,使其得以执行。
    • 分支预测 (Branch Prediction):预测分支的结果,提前取指并执行,如果预测错误,则回滚并重新执行。

# 流水线的异常处理

当流水线中发生异常时,系统需要采取一系列动作来正确处理,确保程序状态的完整性和正确性。

  • 记录异常地址:必须记录产生异常的指令的地址,以便后续处理。
  • 保存和恢复状态:必须保存当前用户程序的状态(如寄存器值),并在异常处理结束后恢复,以便控制权可以交还给用户程序。
  • 控制权转移:异常处理结束后,将控制权交还给用户程序,使其能够从中断处恢复执行。