🐧 第八章 外部存储器
📄 一句话概括

从寄存器到跨洲网络,如果取一次寄存器是 1 秒,取一次内存就是 1.7 分钟,读一次 SSD 是 27.8 小时,读一次机械盘是 147 天,一次跨洲往返是 4.8 年。这本书八章,讲的都是同一件事:怎么让慢的那一层不要拖住快的那一层。

📝 本节和前面几节的关系

8.11 讲完了最后一种介质,这一节收尾:把整本书的八章串成一条线,再看它们用的其实是同一招。

一、一条从纳秒到年的阶梯

Demo 89 把十个层次排成一张表,单位统一成纳秒,再看【如果取一次寄存器是 1 秒】,别的层次各是多少。 寄存器 1 纳秒 → 1 秒;内存 100 纳秒 → 1.7 分钟;SSD 随机读 100 微秒 → 27.8 小时;机械盘随机读 12.7 毫秒 → 147 天;跨洲网络往返 150 毫秒 → 4.8 年。 这个换算的意义在于:CPU 一秒钟能做几十亿次操作,而一次机械盘随机读在【它的时间尺度】上是五个月之后的事。所以【等 IO】不是形容词,是字面意思——CPU 在等的时候,真的什么都做不了。 这也是多进程、异步 IO、DMA 存在的全部理由:既然等不起,那就别等,换别的事做。第 3 章与第 4 章讲的那些状态转换,根源都在这张表里。

二、八章在回答同一个问题

第 1 章说操作系统管四样资源:CPU、内存、外部存储器、输入输出。后面每一章都在回答同一个问题:【怎么让慢的那一层不要拖住快的那一层?】 第 2、3 章:建进程与换代码很贵 → 把 fork 与 exec 分开,用写时复制。 第 4 章:进程切换很贵 → CFS 记账,时间片不预设(避免不必要的切换)。 第 5 章:内存不够、地址翻译慢 → 虚拟内存、多级页表、大页。 第 6 章:内存比 CPU 慢两个数量级 → 三级缓存、TLB、页缓存。 第 7 章:磁盘比内存慢四个数量级 → 日志、写时复制、延迟写。 第 8 章:机械盘一次寻道 12 毫秒 → 合并、调度、预读、换 SSD。 每一章的具体手段都不一样,但套路是同一个。

三、手段只有三招

把八章的手段归一下类,只有三招。 ① 在快的那一层放一份副本。缓存、页缓存、TLB、写时复制,都是这一招。本质是【用空间换时间】,赌你会重复访问。 ② 把慢的那一次访问摊薄到很多次里。批量、预读、合并、大页,都是这一招。本质是【把固定成本分摊到更多数据上】。 ③ 把随机改写换成顺序写。日志、LSM 树、写时复制,都是这一招。本质是【利用机械/物理上的连续性】。 这三招的共同前提:你得先知道【慢在哪一层】,否则优化的是错的地方。这也是为什么每一章都要先做实验。

四、该带走的是方法,不是倍数

这本书里所有【优化】,都是先量出来的。每一章都是【提出问题 → 设计实验 → 看数字 → 解释数字】。 所以真正该带走的不是那些倍数,是这套方法。 第一步:量清尺子。空操作要多长时间?测量的下限在哪里?(8.3) 第二步:把噪声排掉。预热、重复、取最小值、读写分开测。(8.4) 第三步:老实承认哪一侧测不到。测不到就写【这是模型】,不要假装测到了。(8.10) 第 8 章特别适合作为收尾,因为它把这三步演示得最完整:沙箱里没有机械盘,所以大部分结论都是模型——而作者一直在明说这件事。这比给出漂亮数字更难,也更有用。 剩下的就是拿一台真机器,把这些实验重做一遍。做完之后你会发现:数字会变,方法不变。

五、动手 Demo:总结——一张表看完整本书

把从寄存器到跨洲网络的十个层次排成一张表,把所有时间统一成纳秒,再看【如果取一次寄存器是 1 秒】,别的是多少。

ladder = [("CPU 寄存器", 1.0), ("L1 缓存", 1.0), ("L2 缓存", 4.0),
          ("L3 缓存", 15.0), ("内存", 100.0), ("TLB 未命中", 200.0),
          ("SSD 随机读 4 KB", 100000.0),
          ("机械盘随机读 4 KB", 12690000.0),
          ("同机房网络往返", 500000.0),
          ("跨洲网络往返", 150000000.0)]
base = ladder[0][1]

for name, ns in ladder:
    ratio = ns / base
    secs = ratio          # 若寄存器 1 秒,这就是秒数
    if secs < 60:
        human = "%.1f 秒" % secs
    elif secs < 3600:
        human = "%.1f 分钟" % (secs / 60)
    elif secs < 86400 * 3:
        human = "%.1f 小时" % (secs / 3600)
    elif secs < 86400 * 400:
        human = "%.0f 天" % (secs / 86400)
    else:
        human = "%.1f 年" % (secs / (86400 * 365))
    print("%-20s %10.0f 倍  %s" % (name, ratio, human))

实跑输出:

--------------------------------------------------------------------
总结——一张表看完整本书
--------------------------------------------------------------------
① 一条从寄存器到跨洲网络的阶梯

  下面这些数都是【量级】,单位统一成纳秒,**方便直接比倍数**。
  **寄存器和 L1 在同一量级**,表里都按 1 纳秒算。

  层次                    量级           相对寄存器     若寄存器是 1 秒
  -------------------------------------------------------------------------
  CPU 寄存器              1 纳秒         1 倍           1.0 秒
  L1 缓存                 1 纳秒         1 倍           1.0 秒
  L2 缓存                 4 纳秒         4 倍           4.0 秒
  L3 缓存                 15 纳秒        15 倍          15.0 秒
  内存                    100 纳秒       100 倍         1.7 分钟
  TLB 未命中 + 查页表     200 纳秒       200 倍         3.3 分钟
  SSD 随机读 4 KB         100 微秒       100 千倍       27.8 小时
  机械盘随机读 4 KB       12.7 毫秒      13 百万倍      147 天
  同机房网络往返          500 微秒       500 千倍       6 天
  跨洲网络往返            150.0 毫秒     150 百万倍     4.8 年

  **如果取一次寄存器是 1 秒,取一次内存就是 1.7 分钟,读一次 SSD 就是 27.8 小时。**
  **读一次机械盘是 147 天,一次跨洲网络往返是 4.8 年。**

② 于是整本书其实在讲一件事

  **第 1 章说操作系统管四样资源:CPU、内存、外部存储器、输入输出。**
  **后面每一章都在回答同一个问题**:
  **【怎么让慢的那一层不要拖住快的那一层?】**

  章      慢的那一层                  用什么挡住
  -----------------------------------------------------------------
  2~3     建进程与换代码很贵          fork/exec 分开;写时复制
  4       进程切换很贵                CFS 记账,时间片不预设
  5       内存不够、地址翻译慢        虚拟内存、多级页表、大页
  6       内存比 CPU 慢两个数量级     三级缓存、TLB、页缓存
  7       磁盘比内存慢四个数量级      日志、写时复制、延迟写
  8       机械盘一次寻道 12 毫秒      合并、调度、预读、SSD

  **每一条【挡住】的手段,本质都是同一招**:
  **在快的那一层放一份副本,或者把慢的那一次访问摊薄到很多次里。**
  **缓存是【放副本】,批量与预读是【摊薄】,日志与写时复制是【把随机改写换成顺序写】。**

③ 最后一句

  **这本书里所有【优化】,都是先量出来的。**
  **每一章都是【提出问题 → 设计实验 → 看数字 → 解释数字】。**
  **所以真正该带走的不是那些倍数,是【这套方法】**:
  **先想清楚要量什么,再把噪声排掉,最后老老实实承认【哪一侧测不到】。**

  **到这,全书完了。**
  **剩下的就是【拿一台真机器,把这些实验重做一遍】。**

这张表就是整本书的地图。每一章对应其中一行的一段:第 4、5 章在纳秒到微秒,第 6 章在纳秒到百纳秒,第 7、8 章在微秒到毫秒。 机械盘那一行是【147 天】——这个比喻最直观:CPU 觉得一秒钟,机械盘觉得是五个月之后。 跨洲往返 4.8 年:所以分布式系统里【同步调用跨洋服务】这件事,从时间尺度上看是极其昂贵的。这也是 CDN 与边缘计算存在的理由。 最后留意一件事:这张表里没有一项是【算法优化】带来的,全都是【层次】的差距。选对层次,比在同一层里优化一百倍更有效。

30 秒实验

  1. 算一遍:如果一次跨洲往返是 4.8 年,那一万次是多少年?
  2. lscpu | grep -i cache,看看你机器上三级缓存各多大。
  3. 想一想:你写过的代码里,哪一处最常【等 IO】?

常见误区

  1. 【优化就是把代码写快】。八章里没有一章是靠【写更快代码】解决问题的,全是靠【换层次、加副本、摊薄、改顺序】。
  2. 【这些是操作系统的事,应用开发者不用管】。你写的一行 read 可能触发页缓存缺失、预读、块层合并、寻道,懂这些才知道该调什么。
  3. 【知道了倍数就等于懂了原理】。倍数会随硬件变,方法(先量尺子、排噪声、说清边界)不会变。
自测三问(点开对答案) 1. 如果取一次寄存器是 1 秒,读一次机械盘相当于多久? —— **约 147 天。****因为机械盘随机读 12.7 毫秒 ≈ 12.7 百万纳秒,是寄存器的 1270 万倍;1270 万秒就是 147 天。** 2. 八章里的手段可以归成哪三招? —— **① 在快的那一层放副本**(缓存、页缓存、TLB、写时复制);**② 把慢的那一次访问摊薄**(批量、预读、合并、大页);**③ 把随机改写换成顺序写**(日志、LSM 树、写时复制)。 3. 为什么说第 8 章的方法论比它的数字更有价值? —— **因为沙箱里没有机械盘,大部分结论只能是模型**,**而作者一直明确标注【这是模型不是实测】**。**这种【量清尺子、排掉噪声、说清边界】的做法,换成任何领域都用得上。**

相关笔记