ZEVORN.blog

June 5, 2024

浅析 x86 架构的微操作(uops)

note1.7 min to read

uops 词义解析

μops 代指 uop ,全称是 Micro-operation ,中文直译为“微操作”,micro 可以希腊字母 μ 代替,然后 operation 可以缩写为 op ,连一起即 μop。为方便书写,使用 u 代替 μ 。用 uops 来统称所有的 uop ,即 Micro-operations (为了方便,下文统一称为 uops )。

对于 x86 这种 CISC 处理器,为了能在内部更加高效灵活地处理指令,会将长度和复杂度不一的指令,转化成一个或多个 uop,以便更容易被调度和优化,从而达到提升 CPU 性能的目的。

x86 的指令可以理解成是粗颗粒度的,而 uop 可以理解成是细颗粒的微小指令。通过将复杂的粗粒度指令分解成简单的细粒度指令执行,变相获取了 RISC 架构的部分优势,让CPU的整体结构更加紧凑、灵活和高效。

uops 的优势

将指令分割成微操作的主要优点是:

乱序执行

PUSH rbx 指令为例,它将栈指针减少 8 字节,然后将源操作数存储在栈顶。假设在解码后 PUSH rbx 被“破解”成两个依赖的 uop:

SUB rsp, 8STORE [rsp], rbx

通常,函数序言通过使用多个 PUSH 指令保存多个寄存器。在我们的例子中,下一个 PUSH 指令可以在前一个 PUSH 指令的 SUB μop 完成后开始执行,而不必等待当前要执行的 STORE μop。

并行执行

HADDPD xmm1, xmm2 指令为例,它将在 xmm1 和 xmm2 中对两个双精度浮点数进行求和(减少),并将两个结果存储在 xmm1 中,如下所示:

xmm1[63:0] = xmm2[127:64] + xmm2[63:0]xmm1[127:64] = xmm1[127:64] + xmm1[63:0]

微代码化此指令的一种方法是执行以下操作:

  1. 减少 xmm2并将结果存储在 xmm_tmp1[63:0] 中;
  2. 减少 xmm1并将结果存储在 xmm_tmp2[63:0] 中;
  3. 将 xmm_tmp1 和 xmm_tmp2 合并到 xmm1 中。

总共三个 uop。对于步骤 1 和 2 是独立的,因此可以并行完成。

操作融合

有时 uops 也可以融合在一起,现代 CPU 中有两种类型的融合:

微融合:融合来自同一机器指令的 uops。微融合只能应用于两种类型的组合:内存写操作和读改操作。例如:

add eax, [mem]

这条指令中有两个 uops:

  1. 读取内存位置 mem;
  2. 将其添加到 eax。

使用微融合,在解码步骤中将两个 uops 融合成一个。

宏融合: 融合来自不同机器指令的 uops。在某些情况下,解码器可以将算术或逻辑指令与 subsequent 条件跳转指令融合成单个计算和分支 uop。例如:

.loop:  dec rdi  jnz .loop

使用宏融合,将来自 DEC 和 JNZ 指令的两个 uops 融合成一个。

微融合和宏融合都可以节省从解码到退休的所有管道阶段的带宽。融合操作在重新排序缓冲区 (ROB) 中共享单个条目。当一个融合的 uop 只使用一个条目时,ROB 的容量得到更好的利用。

统计 uops

要收集应用程序发出的、执行的和退休的 uops 数量,可以使用 Linux perf,如下所示:

$ perf stat -e uops_issued.any,uops_executed.thread,uops_retired.slots -- ./a.exe2856278 uops_issued.any2720241 uops_executed.thread2557884 uops_retired.slots

指令被分解成微操作的方式可能会随着 CPU 世代的不同而有所差异。通常,用于一条指令的 uops 数量越少,意味着硬件对其支持越好,并且可能具有更低的延迟和更高的吞吐量。对于最新的 Intel 和 AMD CPU,绝大多数指令都会生成恰好一个 uop。

参考资料: [1] 现代 CPU 性能分析与优化 [2] uops 哲学三问