第 1 章 · 计算机系统概述

先看一份真实的输出。在装着 AMD Ryzen 5 5600X 的机器上跑 lscpu,筛掉啰嗦的行,剩下这些:

型号名称:        AMD Ryzen 5 5600X 6-Core Processor
CPU(s):          12
核:              6
线程:            12
CPU 最大 MHz:    4654.2881
L1d 缓存:        192 KiB (6 instances)
L2 缓存:         3 MiB (6 instances)
L3 缓存:         32 MiB (1 instance)

我第一次装完系统盯着这堆数字,只看懂了「6 核、4.6GHz」,其他行直接略过。后来才发现,这份输出几乎就是整门课的目录:6 个 instances 对应 6 个物理核,L1d 的 d 是 data(指令还有一份独立的 L1i,所以没列出来),为什么 L1 只有 32KB 还要分两半、L3 为什么 12 个线程共享一份,分别是第 6 章(流水线)和第 7 章(Cache)的活。这一章先把骨架搭起来:计算机长什么样,「快」由什么决定。

1.1 冯·诺依曼结构:一个用了八十年的决定

1945 年冯·诺依曼等人定下的结构,核心只有一条:指令和数据不加区分,存在同一个存储器里。CPU 取出来的是指令还是数据,只取决于它处在哪个周期、访问哪个地址。

这个决定最大的收益是程序可以被当成数据处理:把下一段程序写进内存,机器就换了个活法。编译器、操作系统、软件更新,全都建立在这条之上。代价也埋在同一处:取指令和取数据走同一条到存储器的路,一次只能干一件,这个「冯·诺依曼瓶颈」会在第 6 章以结构冒险的身份再次出现。

哈佛结构是另一条路线:指令存储器和数据存储器物理分开,两套总线可以同时取指取数。代价是硬件翻倍,而且程序得分装在两块存储器里。它活在两类地方:一类是对吞吐要求极端的 DSP,每个周期要同时读一条指令和两个操作数;另一类是单片机(AVR、不少 Cortex-M)。现代桌面 CPU 玩的是混搭:主存层面是统一的冯·诺依曼,L1 Cache 层面拆成 I-Cache 和 D-Cache,哈佛的那点好处在离 CPU 最近的地方被捡了回来。

1.2 层次结构:ISA 是软硬件的分界线

把整机从下到上看,是一叠层层屏蔽细节的抽象:

第 6 层  应用/算法(用户程序)
第 5 层  高级语言(C / Java / Python)
第 4 层  汇编语言(助记符)
第 3 层  操作系统(进程、内存、文件、I/O)
第 2 层  ISA(指令集架构)——软硬件的分界线
第 1 层  微体系结构(数据通路、流水线、Cache)
第 0 层  数字逻辑(门、触发器)

其中最值得单独说 ISA(Instruction Set Architecture,指令集架构)。它是软硬件之间的合同:以上,编译器和操作系统只认「有哪些指令、哪些寄存器、访存怎么编址」;以下,微架构怎么实现合同完全不归合同管。

这份「实现可以随便换」的自由值多少钱,看 x86 就知道:同一套 ISA,1978 年的 8086 和 2024 年的 Zen 5 内部结构天翻地覆,但几十年前的二进制放到新机器上还能跑。合同没变,履约方式换了一代又一代。第 4 章细看合同条款,第 5、6 章看履约的机器。

1.3 性能三因子:指令数、CPI、主频

主频高不等于快,这是这门课反复矫正的直觉。唯一站得住的度量是 CPU 执行时间,它拆成三个因子:

CPU 执行时间 = 指令数 × CPI × 时钟周期
             = (指令数 × CPI) / 主频

三个因子各归各管:

  • 指令数:由算法、编译器和 ISA 决定。同样的任务,RISC 指令简单,条数通常更多。
  • CPI(每条指令平均周期数):由微架构决定。流水线顺畅、Cache 命中率高,CPI 就低。
  • 时钟周期:由工艺和电路决定,周期 = 1/主频。

三个因子互相牵制。RISC 为了压低 CPI 用定长指令,指令数就上去了;加深流水线让主频上去,分支预测错了要冲刷十几条指令,CPI 反弹(第 6 章)。所以比两个 CPU 谁快,单拎任何一项都是误导,连「主频 × 核数」这种自媒体公式也不行。

两个衍生指标顺便交代。MIPS = 主频/(CPI × 10⁶),跨 ISA 比较没有意义:RISC 单条指令干得少,MIPS 天然虚高。现代更常用 IPC(每周期指令数,即 1/CPI 的倒数关系),它只反映微架构。至于主频,它被电路的关键路径锁死:信号每级逻辑都有延迟下限,周期缩到比关键路径还短,电路就算不完。2004 年前后 Pentium 4 冲到 3.8GHz 后这条路人尽皆知地走不下去了,于是有了那场转向:多核、深流水线、提 IPC。开头那份 lscpu 里「最大 4654MHz」,就是这二十年主频只往前爬了一点的历史遗迹。

1.4 Amdahl 定律:优化要挑大头下手

优化程序时最扎心的公式。设可优化部分占原总时间比例为 f,这部分被加速 p 倍:

加速比 S = 1 / ( (1 - f) + f / p )

两个数代入感受一下。f = 0.5(一半时间可优化),p = 10,S = 1/(0.5 + 0.05) ≈ 1.82;p 加码到 1000,S = 1/(0.5 + 0.0005) ≈ 1.998。一半的代码加速一千倍,整体才快一倍。反过来,f = 0.05 的边角代码,即使加速 100 倍,S 也只有 1.052。

分母里的 (1 - f) 是逃不掉的:就算可优化部分瞬间完成,剩下没动的部分原封不动躺在那里。上限是 1/(1-f)。

它给程序员的行动指令很直接:先跑 profiler 找占时间最多的热点,再决定优化哪里,而不是对着觉得「应该很慢」的地方瞎使劲。往大了说,多核并行也是同一道题:把可并行的 90% 加速 10 倍,S 也才 4.78,剩下的串行部分(锁、共享状态)决定天花板。

1.5 这一章给后面留的钩子

性能公式里,指令数归第 4 章,CPI 归第 6 章(流水线)和第 7 章(Cache),时钟周期归第 3 章(ALU 的加法器延迟就是关键路径)。ISA 合同的文本在第 4 章,履约机器在第 5 章。第 2 章先解决一个更基础的问题:数字在这台机器里怎么表示,顺便解释 0.1 + 0.2 为什么不等于 0.3。

练习

  1. 开头 lscpu 输出里,L1d 有 6 个实例、L3 只有 1 个。结合「每核私有还是全机共享」猜一下 L2 该有几个实例,再回去对答案(输出里写了)。
  2. 某程序 60% 的时间花在内存访问上。把内存访问加速 5 倍,整体加速比是多少?上限呢?
  3. 有人说「A 的主频是 B 的两倍,所以 A 快两倍」。用性能三因子写出这个论断成立需要额外假设的条件。