BLOG

Record, summarize, and improve.

体系结构

pipeline

流水线是用于提高 CPU 速度的基础技术,其中多个指令在执行过程中重叠。

pipelining 引入了几个约束,这些约束限制了良好流动执行。管道危险会妨碍理想的管道行为,从而导致停滞。这三类危害是结构危害、数据危害和控制危害。

  • 结构性危害:由资源冲突引起,即当两条指令争夺同一资源时。
  • 数据危害:由程序中的数据依赖引起,分为三种类型:
    • 先写后读 (RAW) 危险要求在写入后执行依赖读取。
    • 先读后写 (WAR) 危险要求在读取后执行依赖写入。
    • 先写后写 (WAW) 危险要求在写入后执行依赖写入。
  • 控制危害:是由于程序流程的变化而引起的。

ILP

乱序 (OOO) 执行

重新排序指令的过程通常称为 指令调度。调度的目标是以最小化管道危险和最大化 CPU 资源利用率的方式发出指令。指令调度可以在编译时(静态调度)或在运行时(动态调度)完成。

静态调度通过超标量、多执行单元机器的静态调度,调度多执行单元机器使用一种称为 VLIW(非常长指令字)的技术从硬件移动到编译器。其基本原理是通过要求编译器选择正确的指令组合来简化硬件,以保持机器的充分利用。编译器可以使用软件流水线和循环展开等技术来查看比硬件结构合理支持的更远的未来,以找到正确的 ILP。

动态调度为了克服静态调度的问题,现代处理器使用动态调度。动态调度的两个最重要的算法是 Scoreboarding 和 Tomasulo 算法。

Scoreboarding主要缺点是它不仅保留了真正的依赖关系 (RAW),还保留了错误的依赖关系(WAW 和 WAR),因此它提供的 ILP 并不理想。错误依赖关系是由少量架构寄存器引起的。这就是为什么所有现代处理器都采用 Tomasulo 算法进行动态调度的原因。

为了消除错误的依赖关系,Tomasulo 算法利用了 register renameing,性能得到了极大的提高。但是,带有 RAW 依赖项的一系列指令(也称为依赖项链)对于 OOO 执行仍然有问题,因为在寄存器重命名后 ILP 没有增加,因为保留了所有 RAW 依赖项。

实施动态调度的另外两个关键组件是 Reorder Buffer (ROB) 和 Reservation Station (RS)。ROB 是一个循环缓冲区,用于跟踪每条指令的状态,在现代处理器中,它有数百个条目。通常,ROB 的大小决定了硬件可以独立查找 Scheduling 指令的时间。指令按程序顺序插入到 ROB 中,可以不按顺序执行,并按程序顺序停用。当指令放入 ROB 中时,将完成寄存器重命名。

从 ROB 中,指令入到 RS 中,而 RS 的条目要少得多。一旦指令进入 RS,它们就会等待其 input 操作数可用。当输入可用时,可以向相应的执行单元发出指令。

超标量

大多数现代 CPU 都是超标量的,即它们可以在任何给定的周期中发出不止一条指令。Issue width 是在同一周期内可以发出的最大指令数量(参见第 4.5 节)。2024 年主流 CPU 的典型问题宽度在 6 到 9 之间。为了确保适当的平衡,此类超标量引擎还具有多个执行单元和/或管道执行单元。CPU 还将超标量功能与深度管道和乱序执行相结合,以提取给定软件的最大 ILP。

超标量处理器复制执行资源,以保持管道中的指令流动,而不会发生结构冲突。

推测执行

如果指令在分支条件得到解决之前停滞,则控制危害可能会导致管道中的重大性能损失。避免这种性能损失的一种技术是硬件分支预测。使用这种技术,CPU 预测分支的可能目标,并开始从预测路径执行指令(称为推测执行)。

推测性执行的指令在 ROB 中标记为此类。一旦它不再是投机性的,它就可以按程序顺序停用。这是提交架构状态和更新架构寄存器的位置。因为推测指令的结果没有提交,所以当发生错误预测时很容易回滚。

分支预测

正确的预测极大地提高了执行力,因为它们允许 CPU 在没有先前指令结果可用的情况下向前推进。但是,糟糕的投机通常会导致代价高昂的性能损失。现代 CPU 采用复杂的动态分支预测机制,这些机制提供非常高的准确性,并且可以适应分支行为的动态变化。有三种类型的分支可以用特殊方式处理:

  • 无条件跳转和直接调用:它们最容易预测,因为它们每次都被采用并朝着相同的方向发展。
  • 条件分支:它们有两个可能的结果:被接受或不被接受。采用的分支可以向前或向后移动。前向条件分支通常是为 if-else 语句生成的,这些语句很有可能不被采用,因为它们经常表示错误检查代码。向后条件跳转经常出现在循环中,用于进入循环的下一次迭代;这样的分支通常是被采取的
  • 间接调用和跳转:他们有很多目标。可以为 switch 语句、函数指针或虚拟函数调用生成间接跳转或间接调用。函数的返回值值得关注,因为它也有许多潜在目标。

大多数预测算法都基于分支的先前结果。分支预测单元 (BPU) 的核心是分支目标缓冲区 (BTB),它缓存每个分支的目标地址。预测算法在每个周期都会查阅 BTB 以生成下一个地址,从中获取指令。CPU 使用该新地址来获取下一个指令块。如果在当前 fetch 块中没有识别到分支,则下一个要获取的地址将是下一个顺序对齐的 fetch 块(失败)。

无条件分支不需要预测;我们只需要在 BTB 中查找目标地址即可。每个周期 BPU 都需要生成下一个地址,从中获取指令以避免流水线停顿。我们可以只从指令编码本身中提取地址,但我们必须等到解码阶段结束,这将在管道中引入气泡并使事情变慢。因此,必须在获取分支时确定下一个 fetch 地址。

对于条件分支,我们首先需要预测是否会采用该分支。如果没有被拿走,那么我们就会失败,没有必要查找目标。否则,我们在 BTB 中查找目标地址。条件分支通常占总分支的最大部分,并且是生产软件中预测错误惩罚的主要来源。对于间接分支,我们需要选择一个可能的目标,但预测算法可以与条件分支非常相似。

所有预测机制都试图利用两个重要原则,这类似于我们稍后将讨论的缓存:

  • 时间相关性:分支的解析方式可能很好地预测了它在下次执行时的解析方式。这也称为局部相关性。
  • 空间关联:几个相邻的分支可能以高度相关的方式解析(首选的执行路径)。这也称为全局关联。

SIMD 多处理器

在 SIMD 处理器中,单个指令使用许多独立的功能单元在单个周期内对多个数据元素进行操作。向量和矩阵上的运算非常适合 SIMD 架构,因为向量或矩阵的每个元素都可以使用相同的指令进行处理。SIMD 架构可以更高效地处理大量数据,最适合涉及矢量运算的数据并行应用程序。

利用线程级并行性

利用线程级并行 (TLP) 的技术:多核系统、同步多线程(SMT)和混合架构。这些技术可以从可用的硬件资源中勉强产生最大的效率,并提高系统的吞吐量。

内存层次结构

CPU 内存层次结构建立在两个基本属性之上:

  • 时态位置:当访问给定的内存位置时,可能很快就会再次访问相同的位置。理想情况下,我们希望这些信息在下次需要时位于缓存中。
  • 空间位置:当访问给定的内存位置时,可能很快就会访问附近的位置。这是指将相关数据彼此靠近放置。当程序从内存中读取单个字节时,通常会获取更大的内存块(缓存行),因为很多时候,程序很快就会需要该数据。

缓存层次结构