处理器是否可以同时进行存储和算术运算?

Is processor can do memory and arithmetic operation at the same time?

在汇编和处理器的学习中,有一件事让我很兴奋,指令是如何完成的 :

add mem, 1

在我看来,处理器无法加载内存值 在同一条指令中处理算术运算。所以我认为它是这样发生的:

mov reg, mem
add reg, 1
mov mem, reg

如果我考虑具有 RISC Pipeline 的处理器,我们可以观察到一些停顿。像 i++:

这样简单的指令令人惊讶
|  Fetch  | Decode  | Exec    | Memory  | WriteB  |
          |  Fetch  |         |         | Decode  | Exec    | Memory  | WriteB  |
                    |  Fetch  |         |         |         | Decode  | Exec    | Memory  | WriteB  |

(正如我在 Patterson 的书 Computer Architecture: A Quantative Approach 中读到的那样,寄存器在 Decode uOp 中读取,Store/Load 在 Memory uOp 中,我们允许自己在 Memory uOp 中获取寄存器的值。)

我说的对吗?或者现代处理器有特定的方法可以更有效地做到这一点?

没错,现代 x86 会将 add dword [mem], 1 解码为 3 微指令:加载、ALU 添加和存储。 (这实际上是对各种事物的简化,包括英特尔的微融合以及AMD如何始终在管道的某些部分将负载+ ALU保持在一起...)

这3个依赖操作不能同时发生,因为后面的必须等待前面的结果。

但是 独立 指令的执行可能会重叠,现代 CPU 非常积极地寻找并利用“指令级并行性”来 运行 您的代码每个时钟快于 1 uop。参见 this answer for an intro to what a single CPU core can do in parallel, with links to more stuff, like Agner Fog's x86 microarch guide, and David Kanter's write-ups of Sandybridge and Bulldozer


但如果您查看 Intel 的 P6 和 Sandybridge 微体系结构系列,存储实际上是独立的存储地址和存储数据微指令。 store-address 微指令不依赖于加载或 ALU 微指令,可以随时将存储地址写入 。 (Intel的优化手册称之为Memory Order Buffer)。

为了提高前端吞吐量,存储地址和存储数据微指令可以解码为微融合对。对于 add,load+alu 操作也可以,因此英特尔 CPU 可以将 add dword [rdi], 1 解码为 2 个融合域微指令。 (相同的 load+add micro-fusion 用于将 add eax, [rdi] 解码为单个 uop,因此任何“简单”解码器都可以解码它,而不仅仅是可以处理多 uop 指令的“复杂”解码器。这减少了前端瓶颈)。

这就是为什么 add [mem], 1 在 Intel CPU 上比 inc [mem] 更高效,即使 inc reg 与 [=18 一样高效(但更小) =]. (inc 无法对其 load+inc 进行微熔断,它设置的标志与 add 不同)。

但这只是帮助前端更快地将uops送入调度器;加载仍然必须 运行 与添加分开。

但是微熔断负载不必等待整个指令输入的其余部分就绪。考虑像 add [rdi], eax 这样的指令,其中 RDI 和 EAX 都是指令的输入,但是在 ALU 加 uop 之前不需要 EAX。一旦加载地址准备好并且有一个空闲的加载执行单元(AGU + 缓存访问),加载就可以执行。另见 .


registers are read in Decode uOp, Store/Load in Memory uOp and we allow ourselves to take the value of a register at the Memory uOp

所有当前的 x86 微体系结构都使用寄存器重命名的乱序执行(Tomasulo 算法)。指令被重命名并发布到核心的无序部分(ROB 和调度程序)。

直到一条指令从调度程序“分派”到执行单元时,才会读取物理寄存器文件。 (或者对于最近生成的输入,从其他 uops 转发。)


独立 指令可以重叠它们的执行。例如,Skylake CPU 可以维持每个时钟 4 个融合域/7 个非融合域微指令的吞吐量,包括 2 个加载 + 1 个存储,in a carefully crafted loop:

.loop: ; HSW: 1.12c / iter. SKL: 1.0001c
    add edx, [rsp]           ; 1 fused-domain uop:  micro-fused load+add
    mov [rax], edi           : 1 fused-domain uop:  micro-fused store-address+store-data
    blsi ebx, [rdi]          : 1 fused-domain uop:  micro-fused load+bit-manip

    dec ecx
    jnz .loop                ; 1 fused-domain uop: macro-fused dec+branch runs on port 6

Sandybridge 系列 CPUs 有一个 L1d 缓存,每个时钟能够进行 2 次读取 + 1 次写入。 (不过,在 Haswell 之前,只有 256 位向量可以绕过 AGU 吞吐量限制。参见 How can cache be that fast?。)

Sandybridge 系列的前端吞吐量是每个时钟 4 个融合域微指令,并且它们在后端有很多执行单元来处理各种指令混合。 (Haswell 和后来的有 4 个整数 ALU、2 个加载端口、一个存储数据端口和一个用于简单存储寻址模式的专用存储 AGU。因此它们通常可以在缓存未命中停止执行后快速“赶上”,快速乱序的房间 window 找更多工作要做。)