16 MB 文件:顺序整读 14.14 毫秒(1132 MB/秒),随机 4 KB 读 2000 次 28.13 毫秒(278 MB/秒)——每秒读的量差 4 倍。
6.10 讲了缓冲能把小写攒大,这一节看读的那一侧:顺序与随机差在哪三层。
一、实测:4 倍
顺序整读 16 MB:14.14 毫秒,1132 MB/秒。 随机读 2000 次 4 KB(共 7 MB):28.13 毫秒,278 MB/秒。 读了不到一半的量,花了两倍的时间——折成每秒读的量,差 4 倍。 注意这个文件是刚写的、全在页缓存里,所以【磁盘】这一层根本没参与——这 4 倍全是【系统调用 + 预读】的差距。 真去读盘时,第三层的差距会大得多(第 8 章讲机械盘的寻道)。
二、慢在哪三层
第一层:系统调用。顺序读用 1 MB 一块,16 次调用;随机读每次都一次,2000 次。 第二层:页缓存与预读。顺序读时内核一次读进好几页;随机读猜不中,一页一页来。 第三层:存储设备。顺序读是连续扇区;随机读要移动磁头(机械盘)或跳块(SSD)。 三层是叠加的——所以在缓存里测出 4 倍,在磁盘上测出来就是几十倍。
三、预读是一个可调的东西
内核有一个预读窗口,看你顺序访问就把窗口调大,看你不顺序就调小。
所以【顺序访问】不只是省了缓存行,还让内核替你提前搬。
反过来说:随机访问时预读纯属浪费带宽——有些场景(数据库的索引扫描)会显式关掉它。
Linux 上可以按文件设:posix_fadvise(POSIX_FADV_RANDOM)。
四、能变顺序就变顺序
最有效的优化不是【读得更快】,是【把随机变成顺序】。 做法一:批量。把要读的偏移量收集起来,排序,然后一次顺序扫过去——很多数据库的【排序后再回表】就是这个道理。 做法二:按主键顺序读。自增主键比 UUID 好,不光是因为索引,还因为【写与读都变顺序】。 做法三:把小的合并成大的。多个小文件打成一个包,6.8 实测差 52 倍。
五、动手 Demo:读取文件的实验——顺序和随机差在哪
这一节造一个 16 MB 文件,一次顺序整读,一次随机 4 KB 读 2000 次,把每秒读的量算出来对比。
size = 16 * MB
t0 = time.perf_counter()
with open(p, "rb") as f:
while True:
b = f.read(MB)
if not b:
break
seq_ms = (time.perf_counter() - t0) * 1000
t0 = time.perf_counter()
with open(p, "rb") as f:
for pos in poses:
f.seek(pos)
f.read(4 * KB)
rand_ms = (time.perf_counter() - t0) * 1000
实跑输出:
--------------------------------------------------------------------
读取文件的实验——顺序和随机差在哪
--------------------------------------------------------------------
① 16 MB 文件,顺序整读 vs 随机 4 KB 读 2000 次
走法 读了多少 耗时 每秒读
---------------------------------------------------------
顺序整读 16 MB 16.48 毫秒 971 MB/秒
随机读 2000 次 7 MB 27.11 毫秒 288 MB/秒
**随机读只读了 7 MB,却花了 1.6 倍的时间。**
**折成每秒读的量,顺序是随机的 3 倍。**
② 慢在哪三层
这一层 顺序访问 随机访问
-----------------------------------------------------
系统调用 次数少 次数多(每次一次)
页缓存 预读一次读好几页 预读猜不中
存储设备 连续扇区,快 要移动磁头或跳块
**注意这里文件是刚写的、全在页缓存里**,**所以第一层和第二层的差距被压小了。**
**真去读盘时,第三层的差距会大得多**(第 8 章讲)。
③ 所以【预读】是顺序读的加速器
**内核看到你顺序访问,就一次读进好几页。**
**随机访问猜不中,所以一页一页读**——**这就是【随机慢】的第一个原因。**
**对策**:**能让访问变顺序就变顺序**(排序、批量、按主键顺序读)。
顺序 1132 MB/秒,随机 278 MB/秒——差 4 倍。 注意随机只读了 7 MB,却花了两倍的时间。所以【按每秒读多少算】,差距比【按总时间算】更大。 这 4 倍里没有磁盘的份——文件刚写完还在页缓存里。如果没有缓存,随机会慢几十倍。 所以【随机慢】不是玄学,是三层叠加:调用多、预读废、设备跳。 工程上最值钱的一句话:能让访问变顺序就变顺序——排序、批量、合并,比换 SSD 便宜。
📋 Linux 上看预读窗口与实际读盘量:cat /sys/block/sdX/queue/read_ahead_kb;看进程读了多少:cat /proc/<pid>/io(read_bytes 与 rchar 两行)。
30 秒实验
- 把随机次数从 2000 改成 20000,看总耗时是不是 10 倍。
cat /proc/self/io,看 rchar 与 read_bytes 的差(差出来的就是缓存替你挡掉的)。- 把文件换成 1 GB(超过内存),再比一次顺序与随机,差距会明显变大。
常见误区
- 【随机读慢是因为读得少】。慢是因为每次都重新过门、重新定位,与读多少无关。
- 【预读总是好的】。顺序访问时好,随机访问时纯浪费带宽。
- 【SSD 没有随机慢的问题】。SSD 的随机确实快得多,但系统调用与预读这两层的差距还在。
自测三问(点开对答案)
1. 随机读比顺序读慢在哪三层? —— **系统调用次数多、预读猜不中、设备要跳(寻道或跳块)。**三层叠加。 2. 为什么说【能把随机变顺序就变顺序】最值钱? —— **它同时省掉三层**,**而且不需要换硬件**——**排序、批量、合并都是软件能做的事。** 3. 怎么把随机变成顺序? —— **收集偏移量排序后一次扫过去、按主键顺序读、把小文件合并打包。**- ☐ 6.12 写入文件的实验
相关笔记
- 页面缓存 — 缓存替你挡掉了什么
- 写入文件的实验 — 写的那一侧更怕随机
- 文件系统不一致 — 写顺序乱的另一个后果
- 🐧 Linux是怎样工作的 · 索引