从寄存器到机械盘,一次访问的延迟差了几百万倍。把 L1 命中换算成 1 秒,内存要等 40 秒,SSD 要等 11 小时,机械盘要等 46 天。所以每多一次磁盘访问,代价等于让人等半天。
6.1 到 6.13 讲完了每一层,这一节把它们排成一张表,并给出一个【能记住一辈子】的比例尺。
一、全表的量级
寄存器:几百字节,不到 1 纳秒。 L1:64 KB,约 1 纳秒。L2:512 KB,约 4 纳秒。L3:8 MB,约 15 纳秒。 内存:32 GB,约 40 纳秒(不同资料给 40~100,量级一致就行)。 SSD:1 TB,约 40000 纳秒(40 微秒)。 机械盘:4 TB,约 4000000 纳秒(4 毫秒)。 注意最后两行的数量级:SSD 是内存的几千倍,机械盘是几百万倍。这不是【慢一点】,是【差一个世界】。
二、换成人的时间尺度
假设一次 L1 命中 = 1 秒。 那么 L2 = 4 秒,L3 = 15 秒,内存 = 40 秒。 SSD = 11 小时,机械盘 = 46 天。 所以【内存里多绕一圈】相当于让人多等几十秒,还能忍;【磁盘上一次 IO】相当于让人等半个月。 这就是为什么【缓存命中率】值得花大力气优化——每提高一点,省下的都是【半天】。
三、整章在讲什么
缓存(6.1~6.6):让重复访问落在快的那一层。 TLB(6.7):让地址翻译不用每次都查表。 页缓存(6.8):让文件访问不必每次去磁盘。 缓冲与批量(6.9~6.12):把多次小访问攒成一次大访问。 调参(6.13):决定【攒多久】【什么时候写】。 超线程(6.15):让一个核的两个线程填空闲周期。 六件事其实是一件事:把访问留在便宜的那一层。
四、三条能带走的
一、先量再优化。不知道瓶颈在哪一层的时候,任何优化都是猜。 二、改布局优先于改算法。同样的复杂度,常数能差十倍——因为布局决定了命中率。 三、把随机变顺序。排序、批量、合并——软件能做,而且不需要换硬件。
五、动手 Demo:总结——从寄存器到磁盘,一次访问多少钱
这一节把全表排出来,再把它换算成【假设 L1 = 1 秒】时人类能感知的时间,最后把整章的六类手段收成一张表。
rows = [("寄存器", "几百字节", 0.3), ("L1", "64 KB", 1.0),
("L2", "512 KB", 4.0), ("L3", "8 MB", 15.0),
("内存", "32 GB", 40.0), ("SSD", "1 TB", 40000.0),
("机械盘", "4 TB", 4000000.0)]
for name, cap, lat in rows:
print(name, cap, lat, "纳秒")
# 假设 L1 命中 = 1 秒
for name, cap, lat in rows:
print(name, "相当于", lat, "秒")
实跑输出:
--------------------------------------------------------------------
总结——从寄存器到磁盘,一次访问多少钱
--------------------------------------------------------------------
① 全表的量级(不是实测,是业界公认量级)
这一层 典型容量 典型延迟 折成比例
------------------------------------------------------
寄存器 几百字节 0.3 纳秒 0.3x
L1 64 KB 1.0 纳秒 1.0x
L2 512 KB 4.0 纳秒 4.0x
L3 8 MB 15.0 纳秒 15.0x
内存 32 GB 40.0 纳秒 40.0x
SSD 1 TB 40000.0 纳秒 40000.0x
机械盘 4 TB 4000000.0 纳秒 4000000.0x
**注意最后两行的数量级**:**SSD 是内存的几千倍,机械盘是几百万倍。**
**这不是【慢一点】,是【差一个世界】。**
② 换成人的时间尺度
假设**一次 L1 命中 = 1 秒**,那么:
这一层 相当于
---------------------
寄存器 0.3 秒
L1 1.0 秒
L2 4.0 秒
L3 15.0 秒
内存 40.0 秒
SSD 11.1 小时
机械盘 46.3 天
**内存要等 40 秒,SSD 要等 11 个小时,机械盘要等 46 天。**
**所以程序里每多一次磁盘访问,代价就等于让你等半天。**
③ 于是整本书的这一章在说什么
手段 对付什么
----------------------------------------------------------
缓存(6.1~6.6) 让重复访问落在快的那一层
TLB(6.7) 让地址翻译不用每次都查表
页缓存(6.8) 让文件访问不必每次去磁盘
缓冲与异步写(6.9~6.12) 把多次小访问攒成一次大访问
调参(6.13) 决定【攒多久】【什么时候写】
超线程(6.15) 让一个核的两个线程填空闲周期
**一句话总结整章**:**存储是分层的,每层差几十倍到几百万倍。**
**所有性能优化的本质,都是把访问留在便宜的那一层。**
内存 40 秒、SSD 11.1 小时、机械盘 46.3 天。 这个换算不是玩文字游戏——它是【为什么优化要分层】的最好解释。在 40 秒这一层多花点心思,比在 46 天这一层省一点更划算吗?恰恰相反:46 天那一层随便省一次,抵得上 40 秒那一层省几千次。 所以优化的顺序是【从最慢的那一层开始】:先看有没有多余的磁盘 IO,再看内存访问,最后才看缓存行。 大多数人反过来做:一上来就调缓存行对齐,而真正的瓶颈是一次多余的 fsync。
📋 Linux 上看一次操作到底落在哪一层:perf stat -e cache-misses,minor-faults,major-faults——major-faults 是读盘的,最要命的那一个。
30 秒实验
- 把上表的数字全乘 2.5(换成更慢一代的机器),看比例变不变(比例基本不变,所以量级结论是稳的)。
perf stat -e major-faults -r 3 python your.py,看有没有读盘的缺页。- 给自己的一个脚本计时,估算其中磁盘 IO 占多少比例。
常见误区
- 【这些数字必须背精确值】。背量级就够了;不同代硬件差几倍很正常,差一个数量级才算记错。
- 【优化要先优化最快的层】。恰恰相反——最慢的那一层省一次,抵得上最快那层省几千次。
- 【缓存命中率上不去就没救了】。还有【批量】与【顺序化】两条路,往往比调缓存更有效。
自测三问(点开对答案)
1. 为什么说【每多一次磁盘访问,等于让人等半天】? —— **按 L1 = 1 秒的比例,SSD 是 11 小时、机械盘是 46 天。****所以一次多余的磁盘 IO 就是巨大的浪费。** 2. 优化的顺序应该是什么? —— **从最慢的那一层开始**:**先查多余的磁盘 IO,再看内存访问,最后才看缓存行。** 3. 整章的六类手段,共同点是什么? —— **都是【把访问留在便宜的那一层】**——**缓存、TLB、页缓存、批量、调参、超线程全是这个思路。**- ☐ 6.15 超线程
相关笔记
- 总结 — 上一半的收口
- 超线程 — 最后一个小节
- HDD的数据读写机制 — 最慢那一层细讲
- 🐧 Linux是怎样工作的 · 索引