每个程序员都该知道的延迟数字:CPU、内存、磁盘、网络的数量级直觉

写在前面

做后端久了,你会发现所有性能问题本质都是同一个问题:你对各种操作到底有多快、有多慢,没有量级直觉

为什么缓存要分三级?为什么等数据库时不能阻塞线程?为什么一次微服务调用"看起来只是个函数调用"却贵那么多?为什么压缩有时候反而更快?——这些决策的答案,都藏在一张表里:每个常见操作大概要花多少时间

这篇就把这张表摆出来,逐个量级讲清楚,再落到工程决策上。不是让你背绝对值,而是建立"什么东西比什么东西快/慢多少个数量级"的直觉。有了它,你不用压测就能大概估出一个设计靠不靠谱——这就是所谓封底估算(back-of-the-envelope estimation)的底气。

这张表最早来自 Google 的 Jeff Dean 在 2010 年左右的那张著名幻灯片,后来被很多人更新过。本文用 Colin Scott 2020 年的更新版为基准,再补上真实系统里的常见量级。


一、那张经典表:每个操作要多久

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
先看全貌(典型量级,非精确值):

  操作                            延迟            换算成 ns
  ─────────────────────────────────────────────────────────────
  L1 缓存引用                      0.5 ns
  分支预测错误                      5 ns
  L2 缓存引用                      7 ns
  互斥锁加/解锁                     25 ns
  主存(DRAM)引用                  100 ns
  用 Snappy 压缩 1 KB              3 μs           3 000
  1 Gbps 网络发送 1 KB             10 μs          10 000
  SSD 随机读 4 KB                  150 μs         150 000
  内存顺序读 1 MB                  250 μs         250 000
  同一数据中心往返(RTT)           0.5 ms         500 000
  SSD 顺序读 1 MB                  1 ms           1 000 000
  机械硬盘寻道                      10 ms          10 000 000
  机械硬盘顺序读 1 MB              20 ms          20 000 000
  数据包 加州↔荷兰 往返            150 ms          150 000 000

几个一眼能看出来的事实:

  • 从 L1 缓存到主存,差了 200 倍(0.5 ns → 100 ns);
  • 从主存到 SSD 随机读,又差了 1500 倍(100 ns → 150 μs);
  • 从 SSD 到机械硬盘寻道,再差近 100 倍(150 μs → 10 ms);
  • 一切"快"的操作都在纳秒级,一切"慢"的操作都在毫秒级——微秒是分水岭

记住这个"分水岭",比记住任何一个具体数字都重要。

关于这张表的出处和精度:它是 Jeff Dean 2010 年那张著名幻灯片的经典数值集(业界转载数十年的版本),给你的是数量级而非精确值。比如 SSD 随机读 150 μs 是 SATA 时代的数,NVMe 已压到 ~16–100 μs;现代 CPU 的 L1 也更接近 1 ns。比例关系(主存比 L1 慢 200 倍、磁盘比内存慢 10 万倍)才是长期稳定的部分。完整来源与逐年演进见第十节。


二、把 1 纳秒放大成 1 秒

人对纳秒、毫秒没感觉,对秒、分钟、年有感觉。把整张表按 “如果 1 纳秒 = 1 秒” 放大,就能直观体会 CPU 视角下的世界有多慢:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
操作                  实际        放大(1 ns = 1 s)
─────────────────────────────────────────────────────
L1 缓存                0.5 ns     0.5 秒              ← CPU 自己的节奏
L2 缓存                7 ns       7 秒
主存引用               100 ns     约 1.5 分钟          ← 走去茶水间
SSD 随机读             150 μs     约 1.7 天            ← 周末都没回来
同城网络往返           ~2 ms      约 23 天
数据中心往返           0.5 ms     约 5.8 天
机械硬盘寻道           10 ms      约 4 个月            ← 一个学期
跨洲网络往返           150 ms     约 4.75 年           ← 大学都毕业了

CPU 的"一秒"是 L1 缓存级别的瞬间。它要读个主存,相当于你等一杯咖啡;要等一次磁盘寻道,相当于等一个学期;要等一个跨洲网络包,相当于等大学读完。

这就是为什么"CPU 很快、磁盘很慢"不是一句口号,而是差着七八个数量级的鸿沟。 一切性能优化技巧——缓存、批量、异步、数据本地性——本质上都是"别让 CPU 去等那个学期的磁盘"。


三、CPU 与内存:跨不过的"内存墙"

CPU 这十几年频率涨不动了(GHz 顶天),靠的是多核和缓存。真正卡性能的是 CPU 和主存之间那条"内存墙"

1
2
3
4
CPU 想用数据,先找寄存器 → L1(0.5 ns)→ L2(7 ns)→ L3 → 主存(100 ns)→ 磁盘

  每往外走一层,延迟翻几倍到几十倍。
  缓存行(cache line)通常 64 字节:一次搬 64 字节,不是 1 字节。

这条层次结构直接决定了三条写代码的铁律:

  1. 数据本地性(locality) 顺序访问数组,缓存命中,几乎免费;跳跃访问链表节点,每次都可能 miss 主存,慢两个数量级。这就是"同样逻辑,List<T>LinkedList<T> 快得多"的根因——不是算法复杂度,是缓存

  2. 分支预测 if 分支如果不可预测(比如对无序数据做条件筛选),CPU 流水线要付出 ~5 ns 的预测错误代价。把数据先排序再筛选,反而更快——因为分支变得可预测了。这是教科书级别的反直觉。

  3. 结构体布局 把经常一起用的字段放一起、紧凑排列(struct、缓存行对齐、避免伪共享),就是为了让一次缓存行加载尽量多带"有用数据"。.NET 里 Span、Memory、structstackalloc 的存在,全是服务于这条。

这些点在《.NET 高并发编程全景》《.NET 性能优化与 Profiling》里都有 .NET 视角的落地,这里只讲它背后的硬件量级。


四、磁盘:机械、固态、NVMe 三个量级

存储这条线上,硬件代际差着整整三个数量级,而且"随机"和"顺序"是两套世界。

1
2
3
4
5
6
                        随机访问          顺序读 1 MB
─────────────────────────────────────────────────────
机械硬盘(HDD)         寻道 ~10 ms      ~20 ms
SATA SSD                ~150 μs          ~1 ms
NVMe SSD                ~10–100 μs       ~0.5–1 ms
内存(对照)            100 ns           250 μs

几个关键直觉:

  • 机械硬盘的随机访问是灾难性的。一次寻道 ~10 ms,因为它真的有个机械臂在物理移动。HDD 上做随机小 IO,每秒撑死几百次;
  • SSD 把"寻址"从机械运动变成了电子操作,随机访问掉了一个半数量级。所以数据库从 HDD 换 SSD,是"换了个数据库"级别的提升;
  • 但随机 vs 顺序的差距,SSD 仍然存在(哪怕 NVMe)。所以"把随机写变成顺序写"这条铁律,在 SSD 时代依然成立。

这条铁律催生了两个最经典的工程实践:

  • WAL(预写日志):数据库不直接随机改磁盘上的数据页,而是先把改动顺序追加到日志。fsync 一条顺序日志,比随机改 N 个数据页便宜得多。详见《数据库系列(四):事务与 ACID》——里面给过 fsync 的实测对照:HDD ~10 ms / SSD ~1 ms / NVMe ~0.1 ms;
  • LSM 树 / Kafka:把随机写转成顺序写,牺牲一点读放大,换写入吞吐。Kafka 能扛百万级 TPS,根本原因就是它本质上是"顺序追加日志 + 零拷贝",没有任何随机写。

五、网络:从机柜到跨洲

网络的延迟,主要由距离(光速)和跳数(路由器/交换机)决定。同一个机房和跨洲,差着两三个数量级:

1
2
3
4
5
6
7
8
场景                          典型 RTT
──────────────────────────────────────────
同机 / 同机柜                  ~0.1 ms
同数据中心(跨机柜)            0.5 ms
同城(同城双活)                1–3 ms
跨区域(北京 ↔ 上海)           20–30 ms
跨洲(中美 / 中欧)             120–200 ms
地球对跖点(光速理论极限)       ~200 ms

RTT(Round-Trip Time)= 一个数据包打个来回的时间。地球表面两点的 RTT,物理上被光速(光纤里约 2×10⁸ m/s)封顶,再快的网络也突破不了——北京到上海 ~1200 km,光纤走线更长,单程再快也得 ~5 ms,来回就是十几到二十几 ms,这就是"跨城必然 20ms 起"的物理原因。

一个反直觉的点:小请求的延迟是 RTT 主导的,不是带宽主导的

1
2
3
4
5
6
1 Gbps 网络发 1 KB:
  传输时间 = 1 KB / 1 Gbps ≈ 10 μs     ← 带宽部分,很小
  但 RTT(同城) ≈ 2 ms                 ← 这才是你等的时间

  → 一次跨网小请求,你等的是"光跑一个来回",不是"网线粗细"。
    所以"升级带宽"对降低单次小请求延迟几乎没用,得靠"减少 RTT 次数"。

这就解释了为什么 批量(batching)永远有效:把 1000 次"各跑一个来回"合成 1 次"跑一个来回",延迟和往返次数一起降。


六、一次 HTTP 请求到底慢在哪

把上面几节拼起来,拆一次普通的 HTTPS 调用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
冷启动(首次连接,不复用)—— 同机房为例,单次 RTT ≈ 0.5 ms:

  ① DNS 解析        命中本地缓存 <1ms;未命中走递归 10–100 ms
  ② TCP 三次握手     1 RTT        ≈ 0.5 ms
  ③ TLS 握手         TLS 1.3 = 1 RTT(1.2 是 2 RTT)  ≈ 0.5 ms
  ④ 请求 + 响应      1 RTT(小 body 被 RTT 主导)      ≈ 0.5 ms
  ⑤ 服务端处理       业务 + DB,视情况 ~1–10 ms

  冷启动合计 ≈ 3 RTT + 服务端 ≈ 2.5–5 ms(同机房)
  跨城呢?每个 RTT 变 ~25 ms → 冷启动 ≈ 80–90 ms,服务端反而成了零头

两个直接结论:

  • 连接复用是第一优化。冷启动要付 2 个 RTT 的握手税;复用连接(HTTP keep-alive / 连接池)后,每次只剩 1 个 RTT。HttpClient、Redis 连接池、数据库连接池,本质上都是在"省掉那 2 个 RTT"。这也是为什么 new HttpClient() 每次新建连接是个著名反模式——《HttpClient 的前世今生》那篇讲的就是这个坑;
  • 跨城调用时,握手成本会把业务处理时间淹掉。所以"把服务和它的强依赖部署在同一个机房"几乎永远是值的。

七、把这些数字用起来:8 条工程决策

有了量级直觉,下面这些"经验法则"就不再是口口相传的玄学,每条都能对回那张表:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
1. 缓存分层:L1 内存 → L2 Redis → DB
   每跨一层,差 ~100–1000×。本地内存命中 ~ns,Redis ~0.2ms,DB ~1–10ms。
   详见《Redis 学习笔记(二):缓存实战》。

2. 异步 / I/O 并发:等 I/O 时绝不阻塞线程
   I/O 是 ms 级,CPU 是 ns 级——线程等 I/O 是"用 100 万倍贵的东西等便宜的东西"。
   Task.WhenAll 让多个 ms 级 I/O 并发,而不是串行累加。
   详见《.NET 高并发编程全景》《操作系统学习笔记(一):I/O 模型全解》。

3. 批量:把 N 次 RTT 合成 1 次
   单次小请求被 RTT 主导,批量一次发完,往返次数从 N 降到 1。
   管线化(Redis pipeline)、批量插入(EF Core 的 AddRange + 一次 SaveChanges)、
   Kafka 的批量发送,都是这条。

4. 数据本地性:缓存友好的数据布局
   数组 > 链表、紧凑 struct、缓存行对齐、避免伪共享。
   算法复杂度相同,缓存命中决定实际快慢。

5. 顺序写:把随机写变成顺序写
   WAL、LSM、Kafka。这条让 HDD/SSD 的写入吞吐差出数量级。

6. 超时要有依据,别拍脑袋设 30s
   同机房调用设 1s 都算宽;跨城设几百 ms。依据 = RTT × 倍数 + 下游 p99。
   一律 30s 的超时,等于"允许一个慢调用占住线程半分钟",是雪崩的温床。
   详见《后端架构实战(四):微服务的"分布式税"》。

7. 微服务调用的真实成本
   每次跨服务 = 序列化 + 1 RTT + 反序列化 + 对端处理 ≈ ms 级。
   一个用户请求串 5 个服务,光网络就 +5ms+,还得算上重试/超时的尾部放大。
   单体里一个纳秒级的函数调用,到这就是几毫秒——这就是"分布式税"的本金。

8. 压缩的权衡
   压缩 1 KB ≈ 3 μs CPU,但能省下网络传输的 10 μs+/KB。
   跨网传输时压缩几乎总是赚的;同机房、大 CPU、小 body 时可能不赚。

八、现实世界一次操作要多久

经典表给的是硬件/网络的原子操作量级。这一节把它们组装成你日常后端真正会发起的那些操作——读一次 Redis、查一次库、两个 Pod 之间通一次信、穿过一层 sidecar。同样声明在前:全是量级参考,不是精确值,随硬件、CNI、负载、运行时而漂移;尤其 K8s 网络和 sidecar,不同 CNI(Calico/Cilium)和配置能差出几倍。

进程内 / 运行时

操作典型量级
本地函数调用~1 ns
本地缓存命中(IMemoryCache / 字典)~ns–μs
一次系统调用(syscall)~0.1–1 μs
一次线程上下文切换~1–10 μs
一次 JSON 序列化(中等对象)~10–100 μs
Minor GC 暂停~1–10 ms
Full GC 暂停数百 ms–秒级

数据层

操作典型量级
Redis GET(同机房,连接复用)~0.1–0.3 ms
Redis pipeline / MGET(批量,单条摊薄)~几 μs/条
DB 主键查询(buffer pool 命中)~0.5–2 ms
DB 索引范围扫描(返回 100 行)~2–10 ms
DB 查询触发磁盘 IO+ ~1 ms(SSD)/ ~10 ms(HDD)
Elasticsearch 查询(命中分片)~5–50 ms
Kafka 单条 produce(acks=1,同步)~1–5 ms
Kafka 单条 consume(顺序读页缓存)~μs–ms
DNS 递归查询10–100 ms(本地命中 <1 ms)

RPC / 网络层(K8s 视角)

这一层经典表里没有,但微服务时代每天都打。RTT 主导,每跨一跳加一次 RTT

操作典型量级
同 Pod 内(loopback,如 app ↔ sidecar)~10–20 μs
同 Node 跨 Pod(veth pair)~20–50 μs
跨 Node Pod 通信(overlay/VXLAN,同机房)~0.1–0.5 ms
经 ClusterIP(kube-proxy / iptables DNAT)几乎零额外(μs 级,高并发 conntrack 有损耗)
经 Service Mesh sidecar(Envoy,每 hop)2–5 ms(每 hop 额外;Istio 官方 ~3 ms p50 / ~10 ms p99)
经 Ingress / API 网关(TLS + 路由 + 鉴权)+1–5 ms
gRPC 单次调用(同机房,HTTP/2 连接复用)~0.5–2 ms
REST/HTTP 单次调用(同机房,连接复用)~1–3 ms
跨可用区调用(同城)+2–5 ms
跨 region 调用+几十 ms

Service Mesh 的"税"有多重:sidecar 模式下,一个请求要走 app → 本地 Envoy → 网络 → 对端 Envoy → 对端 app,多出两个本地 hop、两次 mTLS 加密。Istio 官方基准给的是 ~3 ms p50 / ~10 ms p99,业界普遍引用 2–5 ms 每 hop;高负载(数千 RPS)时一篇 arXiv 对比测出 Istio 延迟增加可达基线的 166%。这就是为什么 Ambient mesh(sidecar-less,ztunnel)和 eBPF 数据面(Cilium)在抢这块——把 sidecar 从数据路径上挪走。

拆解的乐趣:现实操作 = 原子项之和

把上面这些和经典表对照着看,你会发现真实系统的延迟,基本都能拆回"那张表"里的几个原子项

  • 一次 DB 查询 ≈ 若干次内存访问 + 可能的一次磁盘 IO;
  • 一次同机房 gRPC ≈ 1 个数据中心 RTT + 两端序列化 + 两端处理;
  • 一次穿 Service Mesh 的调用 ≈ 上一条 + 两个 sidecar 的 2–5 ms 税;
  • 一次跨 region 调用 ≈ 基础调用 + 光速封顶的几十 ms RTT。

建立这种"把现实操作拆回原子项“的习惯,是性能估算的核心能力——也是这张表真正的用法。


九、数量级直觉练习

拿几个常见问题练手(答案都在前面那张表里):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
Q1:顺序读一个 10 GB 的文件,内存 / SSD / HDD 分别大概多久?
  → 内存:250 μs × 10240 ≈ 2.5 s
  → SSD:1 ms × 10240  ≈ 10 s
  → HDD:20 ms × 10240 ≈ 200 s(三分钟多)
  同一个文件,HDD 比 SSD 慢 20 倍、比内存慢 80 倍——这就是为什么要"热数据进内存"。

Q2:1000 次 Redis GET(同机房,连接复用),大约多久?
  → 单次 ~0.2 ms。串行:~200 ms。用 pipeline 批量(1 次 RTT):~1 ms 量级。
  → 串行 vs 批量,差两个数量级。

Q3:一个 API 内部要调 3 个下游服务,串行调,跨城,每个 ~25ms RTT。
    改成并行(Task.WhenAll)能省多少?
  → 串行:3 × 25 = 75 ms。并行:max(25,25,25) ≈ 25 ms。
  → 省 50 ms。这是"能用 WhenAll 就别 await 三次"最直观的收益。

Q4:缓存穿透打到 DB,一次查询 5ms。QPS 2000,缓存命中率从 99% 掉到 90%,
    DB 压力翻几倍?
  → 99% 命中:2000 × 1% = 20 次/s 落库。
  → 90% 命中:2000 × 10% = 200 次/s 落库。10 倍。
  → 命中率"只"掉了 9 个百分点,DB 压力翻 10 倍——缓存命中率的杠杆极大。

这种估算不需要跑压测,对着表心算就行。一个能随时做这种估算的工程师,做设计时几乎不会犯"明显不合理"的性能错误。


十、数字从哪来,以及别死记

最后,关于这些数字本身:

  • 出处:本文那张经典表用的是 Jeff Dean 2010 年那张著名幻灯片(“Latency Numbers Every Programmer Should Know”)的经典数值集——业界被转载了十几年的版本。UC Berkeley 的 Colin Scott 把它扩展成按年份滑动的交互图,能看到 1990→现在每个数怎么随硬件漂移;想看最易读的可视化,推荐 samwho 的 2020 版
  • 它们是量级参考,不是精确值。Dean 经典值和 Scott 的现代值在几处差一档(同数量级内):L1 缓存 0.5 ns → 现代 ~1 ns、分支预测 5 ns → ~3 ns、L2 缓存 7 ns → ~4 ns、SSD 随机读 150 μs → NVMe 时代 ~16–100 μs;而主存 100 ns、数据中心 RTT 0.5 ms、磁盘寻道 10 ms 这些长期稳定。硬件还在继续漂移(HBM/CXL 压缩 CPU↔内存鸿沟、QUIC/TLS 1.3 砍握手 RTT)。抓"数量级和相对比例”,而不是死记绝对值——5 年后绝对值会过时,“主存比 L1 慢 200 倍"这种比例关系却长期稳定;
  • 要测自己的系统。生产里的真实数字,永远来自你自己的监控:看 p50 / p95 / p99 / p99.9,不要看平均值(长尾会被平均掉)。平均值是性能世界里最大的谎言之一。

参考资料

想深入,推荐读《Designing Data-Intensive Applications》(Martin Kleppmann)第一章——它对"封底估算"这件事有极好的示范。


小结

  • 一张表:从 L1 缓存(0.5 ns)到跨洲网络(150 ms),跨 8 个数量级;微秒是快慢的分水岭;
  • 人化直觉:把 1 ns 当 1 秒,CPU 读主存 = 等一杯咖啡,等磁盘 = 等一个学期,等跨洲网络 = 等到毕业;
  • CPU↔内存:差 200 倍,缓存层次和数据本地性是一切优化的根基;
  • 磁盘:HDD/SSD/NVMe 差三个量级,“随机 vs 顺序"是永恒命题,WAL/LSM/Kafka 都是把随机转顺序;
  • 网络:RTT 受光速封顶,小请求延迟是 RTT 主导而非带宽,所以批量永远有效;
  • HTTP 请求 = 几个 RTT + 服务端处理,连接复用省掉的就是那 2 个握手 RTT;
  • 现实操作量级:Redis/DB/gRPC 同机房都是 ms 级;K8s 同 Node 跨 Pod 是 μs 级、跨 Node ~0.1–0.5 ms;Service Mesh sidecar 每 hop 再 +2–5 ms;每跨一层网络加一次 RTT;
  • 8 条决策:缓存分层、异步并发、批量、数据本地性、顺序写、有依据的超时、正视微服务调用成本、压缩的权衡;
  • 方法论:抓数量级而非绝对值,看 p99 而非平均,用封底估算做设计判断。

记不住全部没关系——记住"内存比缓存慢 200 倍、磁盘比内存慢 10 万倍、一次跨网至少毫秒级"这三句,日常 90% 的性能判断就够用了。其余的,回来查这张表。

Licensed under CC BY-NC-SA 4.0