专栏文章
专栏文章
计算机原理专栏
1. 计算机原理专栏 #01:CPU 与指令执行 2. 计算机原理专栏 #02:存储体系 3. 计算机原理专栏 #03:IO 与总线 4. 计算机原理专栏 #04:内存模型

计算机原理专栏 #01:CPU 与指令执行

发布于 2026-06-05 06:26 👁 12 次阅读
#computer-architecture#hardware

CPU 与指令执行

本文从冯·诺依曼架构出发,逐层深入讲解 CPU 的工作原理:指令集设计、流水线执行模型、三大冒险及解决方案、分支预测、超标量与乱序执行,最终落脚到性能公式与软件开发的关联。读完能理解"为什么 JVM 的循环嵌套顺序会影响性能"这类问题的底层根因。


目录

章节 说明
冯·诺依曼架构 五大组成部件与存储程序思想
指令集:CISC vs RISC 两种设计哲学与性能公式
CPU 流水线 五级流水原理与吞吐率提升
流水线三大冒险 结构/数据/控制冒险及解决方案
分支预测 静态预测与动态预测算法
超标量与乱序执行 指令级并行的极致优化
SIMD 指令 数据级并行与向量化
CPU 性能公式 三要素与调优方向

冯·诺依曼架构

冯·诺依曼(Von Neumann)在 1945 年的 First Draft of a Report on the EDVAC 中提出了存储程序计算机模型,确立了现代计算机的基础架构。

五大组成部件

部件 现代对应 职责
运算器(ALU) CPU 核心 算术与逻辑运算
控制器(CU) CPU 核心 指令调度与程序流控制
存储器 内存 + 磁盘 存放程序和数据
输入设备 键盘、网卡等 向计算机输入信息
输出设备 显示器、网卡等 从计算机输出信息

存储程序思想的两个核心

  1. 可编程:程序不固化在电路里,可以动态加载不同程序
  2. 存储:程序存放在内存中,可以反复读取执行

任何一台计算机,无论是 PC、服务器还是手机(SoC),都遵循冯·诺依曼体系结构。


指令集:CISC vs RISC

历史背景

早期计算机内存极其昂贵,指令集设计倾向于用可变长度指令压缩存储空间,复杂功能直接用硬件电路实现,这就是 CISC(复杂指令集) 的起源。

1970 年代末,UC Berkeley 的 David Patterson 教授发现:实际程序运行中,80% 的时间使用 20% 的简单指令,由此提出了 RISC(精简指令集) 理念。

对比

维度 CISC(如 x86) RISC(如 ARM、MIPS)
指令长度 可变长度 固定长度(如 32 位)
指令数量 多(数百到数千条) 少(数十到数百条)
单指令复杂度 高,可完成复杂操作 低,只做简单操作
优化目标 减少指令数 减少 CPI
通用寄存器 较少 较多(空出晶体管)
代表 Intel x86/x86-64 ARM、RISC-V、MIPS

Intel 微指令架构:CISC 与 RISC 的融合

从 Pentium Pro 开始,Intel 引入了微指令(Micro-Ops)架构

CISC 机器码
    ↓ 指令译码器(适配器)
RISC 风格微指令(固定长度)
    ↓ 微指令缓冲区
超标量乱序执行流水线

现代 Intel/AMD CPU 已不是纯粹的 CISC,而是 CISC+RISC 融合体。ARM 能战胜 Intel 进入移动市场,核心原因是功耗优先设计(ARM A8 单核 2W vs Intel i7 130W),而非 RISC 架构本身。


CPU 流水线

cpu pipeline

单指令周期处理器的问题

最朴素的设计是单指令周期处理器:每条指令在一个时钟周期内完成。但这要求时钟周期 = 最慢指令的执行时间,导致简单指令也要等待最长时间,主频无法提高。

五级流水线

现代 CPU 把指令执行拆分成独立阶段,让多条指令的不同阶段并行执行

时钟周期:  1    2    3    4    5    6    7
指令 1:   IF   ID   EX   MEM  WB
指令 2:        IF   ID   EX   MEM  WB
指令 3:             IF   ID   EX   MEM  WB
指令 4:                  IF   ID   EX   MEM  WB
指令 5:                       IF   ID   EX   MEM  WB
阶段 英文 职责
取指令 IF (Instruction Fetch) 从内存/Cache 读取指令
指令译码 ID (Instruction Decode) 解析操作码、读取寄存器
执行 EX (Execute) ALU 运算
访存 MEM (Memory Access) 读写内存/Cache
写回 WB (Write Back) 结果写入寄存器

核心收益:虽然单条指令的延迟不变(仍需 5 个时钟周期),但 CPU 的吞吐率提升了,稳定状态下每个时钟周期完成一条指令。

流水线深度的代价

流水线每增加一级,就要多一次写入流水线寄存器的 overhead(约 20ps)。级数越深,overhead 占比越大。现代 ARM/Intel CPU 流水线通常在 14~20 级。

奔腾 4 的 NetBurst 架构将流水线做到 31 级,追求极高主频,但分支预测失败惩罚极大,最终败于 Core 架构。


流水线三大冒险

结构冒险(Structural Hazard)

本质:多条指令在同一时钟周期争用同一硬件资源。

典型案例:第 1 条指令处于 MEM 阶段(访问数据内存),第 4 条指令处于 IF 阶段(取指令,也要访问内存)—— 两者同时需要内存总线。

解决方案:借鉴哈佛架构,将 L1 Cache 拆分为指令缓存(I-Cache)数据缓存(D-Cache),使取指和访存可以并行,互不干扰。

数据冒险(Data Hazard)

本质:后续指令依赖前面指令尚未写回的计算结果。

三种依赖关系:

依赖类型 英文 说明
数据依赖 RAW (Read After Write) 先写后读,最常见
反依赖 WAR (Write After Read) 先读后写
输出依赖 WAW (Write After Write) 写后再写

解决方案

  1. 流水线停顿(Pipeline Stall / Bubble):插入 NOP 指令等待,简单但牺牲性能
  2. 操作数前推(Operand Forwarding):在硬件上增加旁路,把 EX 阶段的计算结果直接转发给下一条指令的 EX 阶段输入,无需等待写回
  3. 乱序执行(Out-of-Order Execution):调度器找到没有依赖关系的后续指令提前执行

控制冒险(Control Hazard)

本质:遇到条件跳转指令(if/else、循环)时,CPU 不知道下一条该执行哪条指令。

解决方案

方案 说明 特点
流水线停顿 等待跳转结果确定再取指 正确但代价高
缩短分支延迟 把条件判断提前到 ID 阶段 减少等待周期数
静态分支预测 "假装分支不发生",预测顺序执行 简单,约 50% 准确率
动态分支预测 根据历史跳转记录预测 准确率可达 93%+

分支预测

静态预测

最简单的策略:假装分支不发生,即始终预测顺序执行。预测失败时,将流水线中已执行到一半的指令 Flush(清除),重新取指。

动态分支预测

1 比特饱和计数(一级分支预测):用 1 bit 记录上次跳转结果,直接用上次结果预测下次。

2 比特饱和计数(双模态预测器):用 4 状态机,需连续两次"反转"才改变预测方向,更稳定:

强不跳转 ←→ 弱不跳转 ←→ 弱跳转 ←→ 强跳转

2 比特预测器在 SPEC 89 测试中准确率达 93.5%,Intel Pentium 时代使用此方案。

软件开发影响:循环嵌套顺序

// 方案 A:内层循环 10000 次
for (int i = 0; i < 100; i++)
    for (int j = 0; j < 1000; j++)
        for (int k = 0; k < 10000; k++) { }
// 分支预测失败次数:100 × 1000 = 10 万次

// 方案 B:内层循环 100 次
for (int i = 0; i < 10000; i++)
    for (int j = 0; j < 1000; j++)
        for (int k = 0; k < 100; k++) { }
// 分支预测失败次数:10000 × 1000 = 1000 万次

实测方案 B 比方案 A 慢约 3 倍。内层循环次数越多,分支预测失败越少,性能越好。


超标量与乱序执行

超标量(Superscalar)

单个时钟周期内同时发射多条指令到不同的执行单元(ALU、FPU、Load/Store 等),使 CPI < 1。

时钟周期 N:  指令 1 → ALU1
              指令 2 → ALU2
              指令 3 → FPU

现代 CPU 通常是 4~6 路超标量。

乱序执行(Out-of-Order Execution)

CPU 内部维护一个指令调度队列(Reservation Station),找出没有数据依赖的指令提前执行,打破程序顺序约束。结果通过重排序缓冲区(ROB) 按原始顺序提交,对外表现仍是顺序执行。

乱序执行是 CPU 自动完成的,对程序员透明。但在多核场景下,需要通过内存屏障(Memory Barrier)来约束编译器和 CPU 的重排序行为。


SIMD 指令

SIMD(Single Instruction Multiple Data):一条指令同时操作多个数据,实现数据级并行

标量加法:  a[0]+b[0], a[1]+b[1], a[2]+b[2], a[3]+b[3]  → 4 条指令
SIMD 加法:  [a[0],a[1],a[2],a[3]] + [b[0],b[1],b[2],b[3]]  → 1 条指令
指令集 寄存器宽度 适用场景
SSE/SSE2 128 bit 早期多媒体处理
AVX/AVX2 256 bit 图像处理、科学计算
AVX-512 512 bit 深度学习推理

开发关联:Java 从 JDK 16 开始通过 Vector API 暴露 SIMD 能力;JVM JIT 编译器也会自动对某些循环进行向量化优化。


CPU 性能公式

$$\text{程序执行时间} = \text{指令数} \times \text{CPI} \times \text{时钟周期时间}$$

要素 含义 优化方向
指令数 程序执行的总指令条数 编译器优化、算法优化、CISC 减少指令数
CPI 每条指令平均时钟周期数 流水线、超标量、乱序执行、减少 Cache Miss
时钟周期时间 1 / 主频 提升主频(受功耗墙限制)

功耗墙(Power Wall):主频提升导致功耗以立方级增长,散热无法解决,这是为什么 2004 年后单核主频停止大幅提升,转向多核路线的根本原因。


参考资料

  • 《深入浅出计算机组成原理》— 极客时间,郑晔
  • 《计算机组成与设计:硬件/软件接口》— Patterson & Hennessy,第 4 章
  • 《深入理解计算机系统》(CSAPP)— 第 4 章
← 返回列表

评论 (0)

暂无评论,来留下第一条吧。

发表评论