30. 计算机组成原理

自底向上理解计算机如何执行程序:指令集架构(RISC 与 CISC)的取舍、数据的机器表示与浮点标准、CPU 数据通路与控制器、五级流水线的设计、结构冒险/数据冒险/控制冒险的成因与解决、存储层次与缓存局部性,以及 CPI、Amdahl 定律等性能度量方法。

1. 从晶体管到计算机

1.1 抽象层次

计算机是一层层抽象堆叠起来的系统,每一层只暴露少量接口给上一层:

应用程序 / 高级语言
      ↓ 编译
指令集架构 ISA(机器指令、寄存器、地址空间)
      ↓ 微架构实现
微架构(流水线、乱序执行、分支预测、缓存)
      ↓
数字逻辑(门电路、触发器、加法器、多路选择器)
      ↓
晶体管 / 半导体物理

关键认知:ISA 是软硬件契约,微架构是实现方式。同一份 x86 机器码,Intel 与 AMD 用完全不同的内部微架构执行,结果一致。

1.2 冯·诺依曼结构

经典模型由五部分组成:运算器、控制器、存储器、输入、输出,核心特征是存储程序、指令与数据同存储器、顺序执行。现代计算机仍是冯·诺依曼结构,只是把运算器与控制器合并为 CPU,并引入流水线与缓存缓解「存储墙」。

2. 指令集架构 ISA

2.1 RISC 与 CISC

维度RISCCISC
代表ARM、RISC-V、MIPSx86、早期 VAX
指令长度定长(如 4 字节)变长
指令数量少而精简多而复杂
访存方式Load/Store 架构算术指令可直接访存
译码难度简单、易流水线复杂、需微码
典型场景移动、嵌入式、服务器新势力PC、服务器存量

工程现实:现代 x86 前端会把复杂指令翻译成类 RISC 的微操作(uop)再执行,两种路线已在微架构层面趋同。

2.2 指令的构成

一条指令通常包含:操作码(opcode)+ 源操作数 + 目的操作数 + 寻址方式。

# RISC-V 风格:load/store 架构
lw   t0, 0(sp)      # 从内存读一个字到 t0
add  t1, t0, t2     # 寄存器相加
sw   t1, 8(sp)      # 写回内存

2.3 常见寻址方式

  • 立即数寻址:操作数就在指令里。
  • 寄存器寻址:操作数在寄存器。
  • 基址 + 偏移:[base + offset],数组与结构体访问的主力。
  • PC 相对寻址:用于跳转与位置无关代码(PIE)。

3. 数据的机器表示

3.1 整数

  • 无符号:直接二进制。
  • 有符号:补码,把减法统一为加法,溢出静默环绕。
  • 字节序:小端(x86、ARM 默认)低位在低地址;大端(网络字节序)高位在低地址。

3.2 浮点数 IEEE 754

单精度 32 位 = 1 符号位 + 8 阶码 + 23 尾数;双精度 64 位 = 1 + 11 + 52。阶码用移码表示,尾数隐含前导 1。

+---+--------+-----------------------+
| S | 阶码 E |      尾数 M            |
+---+--------+-----------------------+
值 = (-1)^S × 1.M × 2^(E-127)     (规格化数)

浮点数的三大坑:不能精确表示 0.1、大数吃小数、NaN 不等于自身。金额计算用定点数或十进制库,别用 float。

3.3 代码示例

#include <stdio.h>
int main(void) {
    float a = 0.1f, b = 0.2f;
    printf("%.17f\n", (double)(a + b));   // 0.30000001192092896
    printf("%d\n", a + b == 0.3f);        // 0
    return 0;
}

4. CPU 数据通路与控制器

4.1 五大部件

    PC ──▶ 指令存储器 ──▶ 译码 ──▶ 控制信号
     │                              │
     ▼                              ▼
  寄存器堆 ◀── ALU ──▶ 数据存储器(Load/Store)
  • PC(程序计数器):指向下一条指令地址。
  • 寄存器堆:通用寄存器集合,读写端口有限(通常 2 读 1 写)。
  • ALU:算术逻辑单元,做加减、与或、移位、比较。
  • 控制器:把指令译码为各部件控制信号;硬布线快、微程序灵活。

4.2 单周期 vs 多周期

  • 单周期:一条指令一个时钟周期完成,时钟周期由最慢指令决定,硬件简单但效率低。
  • 多周期:把指令拆成取指、译码、执行、访存、写回若干步,每步一个周期,硬件复用但状态机复杂。
  • 流水线:多周期 + 阶段重叠,是现代主流。

5. 流水线

5.1 五级经典流水线

周期:    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
  • IF 取指、ID 译码取数、EX 执行、MEM 访存、WB 写回。
  • 理想情况下,n 条指令耗时 ≈ (n + 4) 个周期,吞吐接近每周期一条。
  • 流水线寄存器保存阶段间的中间结果。

5.2 加速比公式

加速比 = 串行执行时间 / 流水线执行时间
       = (k × n × T) / ((n + k - 1) × T)   → k(n 很大时)

其中 k 是级数。理想加速比上限就是流水线级数,且级数越多,冒险代价与寄存器开销越大,实际收益递减。

6. 冒险与处理

6.1 三类冒险

冒险成因解决手段
结构冒险硬件资源冲突(同时访存)增加端口、指令/数据缓存分离
数据冒险后指令依赖前指令结果转发(旁路)、插入气泡、编译器调度
控制冒险分支改变 PC分支预测、延迟槽、预取两条路径

6.2 数据冒险与转发

add  x1, x2, x3   # 第 3 周期才算出 x1
sub  x4, x1, x5   # 第 4 周期需要 x1 —— 若等到 WB 则需暂停 3 拍

**转发(forwarding/bypass)**把 EX 阶段的结果直接送回 EX 输入,通常可消除 1~2 拍停顿;load-use 冒险无法用转发消除(数据在 MEM 末才可用),必须插一个气泡,或由编译器重排指令填充。

6.3 分支预测

  • 静态预测:总是预测不跳转,或向后跳转预测跳。
  • 动态预测:两位饱和计数器、两级自适应、现代 TAGE 预测器,准确率可达 95% 以上。
  • 预测失败代价:清空流水线重取,深流水线代价可达十几拍。

7. 存储层次与缓存

7.1 金字塔

寄存器    ~1 周期      几百字节 ~ 几 KB
L1 缓存   ~4 周期      32 ~ 64 KB
L2 缓存   ~12 周期     256 KB ~ 1 MB
L3 缓存   ~40 周期     几 MB ~ 几十 MB
主存      ~200 周期    几十 GB
SSD       ~10 万周期   几百 GB ~ TB
磁盘      ~千万周期    TB 级

核心规律:越靠近 CPU 越快越小越贵;层次结构之所以有效,是因为程序具有时间局部性与空间局部性。

7.2 缓存映射方式

  • 直接映射:每个内存块只能放一个固定行,冲突率高、查找快。
  • 全相联:任意行,冲突最低、硬件比较器多。
  • 组相联:折中方案,N 路组相联是现代主流。

地址被划分为 标记 Tag | 组索引 Set | 块内偏移 Offset,查找时按组索引定位,再并行比较各路的 Tag。

7.3 写策略与替换

  • 写直达(write-through):同时写缓存与内存,简单但慢。
  • 写回(write-back):只写缓存并置脏位,替换时才写回,需处理多核一致性。
  • 替换算法:LRU 近似(时钟算法)、随机;多核还需 MESI 等一致性协议。

7.4 代码示例:局部性差异

#define N 1024
int a[N][N];
long sum = 0;

// 行优先遍历:顺序访存,缓存友好
for (int i = 0; i < N; i++)
    for (int j = 0; j < N; j++)
        sum += a[i][j];

// 列优先遍历:跨行跳跃,每步都可能缺页/缺行,慢数倍
for (int j = 0; j < N; j++)
    for (int i = 0; i < N; i++)
        sum += a[i][j];

8. 性能度量与优化

8.1 三个基本公式

CPU 时间 = 指令数 × CPI × 时钟周期
        = 指令数 × CPI / 主频

加速比(Amdahl) = 1 / ((1 - P) + P / S)
  • CPI:每指令周期数,流水线理想为 1,受冒险影响上升。
  • Amdahl 定律:优化只占 P 比例的部分、加速 S 倍,整体收益被未优化部分封顶。P=0.5、S=∞ 时上限只有 2 倍。

8.2 优化优先级

  1. 减少指令数:算法优化、向量化、减少冗余计算。
  2. 降低 CPI:提升分支预测、减少缓存缺失、提高指令级并行。
  3. 提高主频:受功耗与散热限制,已接近物理天花板。

面试常问:为什么现在不靠堆主频?因为功耗近似与频率的三次方成正比(动态功耗 ∝ C·V²·f,而电压随频率需同步提高),堆核心数与并行度才是出路。

9. 常见陷阱

  • 用浮点数比较相等:应判断差值绝对值是否小于 epsilon,或改用整数/定点。
  • 忽视字节序:网络传输与二进制文件读写必须显式转换。
  • 以为流水线级数越多越好:深流水线提升吞吐但加剧冒险惩罚与功耗。
  • 忽略缓存行伪共享:多线程写同一缓存行内不同变量会互相失效,需填充对齐。
  • 混淆 CPI 与延迟:高吞吐(低 CPI)不等于低延迟,需按目标选择指标。
  • 过早优化:未测量就假设瓶颈在 CPU,实际瓶颈常在 IO 或锁竞争。

参考文章

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「计算机基础」更多文章

  1. 34. 编程范式与类型系统
  2. 33. 分布式系统基础
  3. 32. 加密与安全基础