卷 VI · 落地CH 24深度 24/24

骗局的价目表

这本书从头到尾在说一件事:你以为你独占整台机器,而维持这个假象是要花钱的。每一章都往账单上添了一行。现在,把它们放在一起。

完整价目表七个数量级怎么用它读完之后

整张表

▶ 动手 · 骗局的价目表

点每一行看它是什么、来自哪一章。把「做多少次」的滑块拖到你的实际量级,看总账。

注意条形是对数刻度——不这么画的话,前十行会挤成一条看不见的线。

先感受一下这个跨度

表格最上面那行是 1 ns,最下面那行是 8 ms。比例是八百万倍

这个数字大到失去直觉了,所以换个说法。把 1 纳秒当成 1 秒,整张表变成:

操作真实耗时如果 1 ns = 1 秒
L1 缓存命中1 ns1 秒
主存访问80 ns1 分半
一次系统调用100 ns2 分钟
走一趟四级页表120 ns2 分钟
进程上下文切换3 μs50 分钟
NVMe 随机读 4 KB100 μs1 天多
同机房一个来回500 μs6 天
一次 fsync1 ms11 天
一次主缺页3 ms1 个多月
机械盘一次寻道8 ms3 个月

一秒钟,和三个月。这就是你每天在写的代码里,两条相邻语句之间可能存在的差距。

这张表最重要的用法

◆ 一个新习惯

看见任何一段代码,先问:它在这张表的第几行?

然后你会立刻发现,绝大多数「性能优化」是在错误的行上使劲。

举个具体的:你花了一下午把一个跑一万次的循环优化了 30%,省下 3 μs。同一个函数里有一次 fsync,值 1000 μs

你的一下午,对总耗时的贡献是 0.3%。

而如果你把那次 fsync 和另外 99 次合并成一次批量提交(第 18 章那个组提交),你能省下 99%。五分钟的改动,三个数量级的收益。

这不是说微观优化没意义——在已经确认热点在那一行时它很有意义。问题在于「确认」这一步经常被跳过,而人的直觉在跨七个数量级的问题上完全不可靠

三条从表里直接读出来的经验

1. 批量化:把固定成本摊薄

表里很多操作有固定成本——不管你办多少事,进门那一趟的钱是一样的。系统调用 100 ns,你读 1 字节和读 128 KB 都要付。

于是「攒一批一次办」这个模式在各处反复出现,而且现在你能看出它们是同一件事:

你见过的做法摊薄的是哪一行本书哪章
stdio 的缓冲区 / BufferedInputStream系统调用第 3 章
数据库的组提交fsync第 18 章
网络的 Nagle 算法、批量发包网络往返第 20 章
RecyclerView 的批量 notify、Compose 的重组批处理上下文切换 + 渲染第 9 章
Flow 的 bufferconflate、日志的异步落盘系统调用 + 切换第 3、9 章

它们看起来是五个不同领域的技巧,其实是同一条原则的五次应用。

2. 局部性:让你留在表的上半部分

表的前几行(缓存、TLB)和后面差着几个数量级,而你能不能留在前几行,几乎完全取决于访问模式

顺序访问一个数组:缓存预取生效,一个 TLB 条目管 1024 个 int。你活在第 1 行。

随机访问同样大的数组:几乎每次都 cache miss,而且很可能 TLB miss。你掉到了第 3–5 行,慢两百倍。

数据结构的选择、内存布局、遍历顺序——这些「看起来很基础」的东西之所以影响巨大,就是因为它们决定了你在这张表的哪一段。这也是为什么面向数据的设计(数组的 struct 而不是 struct 的数组)在性能敏感场合效果显著。

3. 异步化:不是让它变快,是别让人干等

表下半部分那些毫秒级的操作,你没法让它们变快——磁盘就那么快,网络就那么远。

能做的是:等待的时候别占着 CPU。这就是异步 IO、epoll、协程存在的全部理由(第 19 章)。

注意这个区别很关键:异步不会让单次操作变快,它让你在等待期间能干别的。如果你的负载是纯 CPU 密集的,异步化一点用都没有,甚至因为多了调度开销而更慢。

关于这些数字的诚实说明

⚠ 别把这些数字当成实测值

这张表是现代 x86-64 Linux 上的典型量级。换台机器、换个内核版本、换块盘,每一行都会浮动,有些能浮动好几倍:

  • 系统调用在 KPTI 之后明显变贵(第 3 章),而有没有 PCID 又影响很大。
  • NVMe 的延迟,消费级和企业级能差三倍。
  • fsync 极度依赖硬件——有没有带电池的写缓存,能差一个数量级。
  • 上下文切换的间接成本(第 9 章)完全取决于你的工作集大小。

但它们之间的比例关系是稳的,而那才是你需要记住的东西。

要拿到你自己机器上的真实数字,可以用:perf bench(系统调用、内存、调度)、fio(磁盘)、lmbench(综合)、sysbench量一遍你自己的机器,比背这张表有用得多。

回到开头那个谎言

第 1 章说:你的程序有一个从没被质疑过的信念——这台机器是我的

现在你知道了这个信念的全部内幕:

◆ 三重假象,一道边界,一张价目表
  • 你以为你独占 CPU——其实你每秒被抢走上千次控制权,靠一个硬件时钟信号维持秩序,而调度器用一只按权重走快慢的虚拟表决定谁下一个上(卷 II)。
  • 你以为你独占内存——其实你写的每个地址都要被切成 9|9|9|9|12 走完四级页表,而「分配成功」只是一句承诺,兑现发生在你第一次动手写的时候(卷 III)。
  • 你以为你在读一个文件——其实你只拿到了一个数组下标,而文件本身连名字都没有(卷 IV)。

撑着这三重假象的,是硬件上那两个比特(卷 I)。而每一次你跨过那道由两个比特划出的线,都要付一次钱——那些钱,就是这张表

最后你还亲手把整个骗局又演了一遍,那就是容器(卷 V)。

接下来读什么

这本书的目标是画一张地图,不是穷尽细节。如果你想继续往下走:

你想去读为什么是它
系统地重学一遍,带练习《Operating Systems: Three Easy Pieces》免费在线,三大主题正好对应虚拟化/并发/持久化。写得极其好读,是这本书最自然的下一站
理解机器本身,而不只是 OSCSAPP(深入理解计算机系统)从二进制表示一路讲到链接、异常控制流、并发。第 9 章那本书讲得比这里深得多
亲手写一个内核xv6MIT 的教学 Unix,几千行 C,配套课程和实验都公开
深入 Linux 具体实现《Linux 内核设计与实现》/内核文档这本书讲机制,那些讲 Linux 是怎么实现这些机制的
性能分析实战Brendan Gregg 的《Systems Performance》第 23 章那套排障方法的完整版,工具和方法论都极其系统

最后一句

这本书没打算让你成为内核开发者。它想给你的东西朴素得多:

一双能看见那道线的眼睛。

它一直在那儿。你写的每一行代码都活在它上面,你调用的每一个库函数迟早都要跨过它,你遇到的每一个「玄学」性能问题都是它在收账。

以前它是隐形的,所以那些现象看起来互不相干、无从下手。现在你能看见它了——于是那些现象会开始互相解释

祝你在下一次撞见段错误、OOM、或者一个说不清的延迟尖刺时,能笑一下,然后知道该先敲哪条命令。