系统基础¶
一段程序从来不只运行在某一层。源代码经过编译器变成指令,指令在乱序核心中推进,数据穿过寄存器、缓存、内存与设备,内核负责地址空间、调度与 I/O,语言运行时又在这些机制之上恢复更安全的抽象。系统学习的主线,正是理解这些层之间的契约与成本。
从哪里开始¶
- 系统抽象与性能度量:先学会区分规范、实现和观测,再建立延迟、吞吐、利用率、并行度与排队之间的定量关系。
- 数据表示与内存层次:理解同一组比特怎样成为整数、浮点数、指针与指令,以及局部性为何决定现代程序的大量性能差异。
- ISA 与微架构:把软件可见的指令契约和处理器内部实现分开。
- 进程与线程:观察内核如何把地址空间、执行流和资源引用组合成可调度实体。
- 并发的语义基础:从 happens-before 与所有权出发,而不是从“该选哪一种锁”出发。
一张跨层地图¶
| 层次 | 主要契约 | 常见隐藏成本 | 首要观测手段 |
|---|---|---|---|
| 语言与运行时 | 类型、对象生命周期、异常与并发语义 | 分配、边界检查、GC、调度 | 编译器报告、运行时 trace |
| 编译器与 ABI | 调用约定、布局、优化后的程序等价性 | 向量化失败、寄存器溢出、间接调用 | IR、反汇编、优化备注 |
| ISA | 指令、寄存器、异常、内存访问语义 | 指令数不是周期数 | PMU、硬件 trace |
| 微架构 | 实际取指、执行、缓存与预测 | miss、错误预测、端口争用 | 性能计数器、微基准 |
| 操作系统 | 进程、虚拟内存、文件、调度与隔离 | 缺页、上下文切换、回写、排队 | perf、eBPF、/proc |
| 设备与互连 | 队列、DMA、一致性与完成通知 | 传输、批处理、NUMA、队头阻塞 | 设备计数器、tracepoint |
任何表格中的一格都不是孤岛。例如,一次看似普通的 C++ 数组访问,可能同时受对象布局、编译器别名分析、向量 ISA、缓存行、页表、NUMA 放置和调度迁移影响。定位问题时应沿数据和控制流逐层缩小范围,而不是先猜一个“经典原因”。
三条阅读纪律¶
先写清工作负载¶
性能没有脱离工作负载的绝对值。至少记录输入规模与分布、并发度、读写比例、热身策略、持续时间、软硬件版本和功耗策略。相同程序在冷缓存、热缓存、内存压力或频率受限时回答的是不同问题。
先验证语义,再优化路径¶
优化不能越过语言、ISA 或内核的契约。数据竞争、越界、失效的对象生命周期和错误的持久化假设,可能在某台机器上“恰好工作”,但不能由一次测量升级为保证。
用模型提出假设,用计数器证伪¶
模型把复杂系统压缩为少数关键变量;计数器和 trace 用来判断哪些变量真的控制了结果。模型不是装饰公式,计数器也不是指标收藏。两者必须围绕同一问题闭环:
\[
\text{现象}\rightarrow\text{假设}\rightarrow\text{可观测预测}\rightarrow\text{实验}\rightarrow\text{修正模型}
\]
学习闭环¶
每个主题都可以用同一组问题检查:
- 它解决了什么资源或语义矛盾?
- 对上层承诺什么,对下层依赖什么?
- 正常路径与慢路径的状态如何变化?
- 正确性依赖哪些顺序、所有权和生命周期不变量?
- 成本随输入、并发和硬件怎样缩放?
- 哪些失败模式只在压力、崩溃或弱内存序下出现?
- 怎样通过一个可复现实验区分相邻假设?