顺序写 16 MB 用 12.99 毫秒,随机写只有 7 MB 却要 21.95 毫秒——按每 MB 折算贵 3 倍。写比读更怕随机:一次随机写可能变成【一读一写】。
6.11 讲了读的随机慢,这一节讲写的随机为什么更慢——这也是【日志用追加写】的根本原因。
一、实测:每 MB 贵 3 倍
顺序写 4096 次 4 KB(16 MB):12.99 毫秒。 随机写 2000 次 4 KB(7 MB):21.95 毫秒。 按总量看差 1.7 倍;按每 MB 折算,贵 3 倍。 为什么【按每 MB 算】差距更大:因为随机的每一次都有固定开销(定位、可能读回来、记元数据),跟写多少无关。 所以【写小块的随机】是最差的组合——固定开销摊不开。
二、写比读多四笔账
一、要先读回来。你改一页里 4 KB,文件系统可能得先把那一页整页读进来(如果块比页小,或者要校验)。 二、要写回去。所以一次随机写可能变成【一读一写】两趟。 三、要记元数据。块的分配表、inode 的时间戳、空闲块位图都得跟着改——这些又是额外的随机写。 四、设备有写放大。SSD 按块擦写:改 4 KB,实际可能动了 256 KB。所以【小块随机写】在 SSD 上寿命和性能双输。
三、所以日志都追加写
追加(append)天生顺序:不用找位置、不用读回来、不用改中间的块。 而且追加的内容永远在文件末尾——元数据只需要改【文件大小】一个字段。 所以【日志 + 追加写】是同时拿到了【顺序】和【少元数据】两个好处。 数据库的 WAL、消息队列的分段文件、LSM 树的 SSTable,全是这个思路。 代价是【读的时候要合并】——用读的复杂度换写的速度。
四、一个立刻能用的结论
写文件时尽量不要【跳着写】:先攒在内存里,最后一次性顺序写出。
能用追加就别用原地改。
要改文件中间的内容,宁可直接重写一个新文件再替换(写临时文件 + rename)——rename 是原子的,而且全程顺序写。
这条在 SSD 上依然成立(写放大),在机械盘上是十倍以上的差距。
五、动手 Demo:写入文件的实验——顺序写和随机写差在哪
这一节往 16 MB 的文件里写 4 KB 的块,一次从头顺序写,一次跳着随机写,再按【每 MB 花多少时间】折算。
size, blk, n = 16 * MB, 4 * KB, 2000
data = b"\x7E" * blk
with open(p, "r+b") as f:
t0 = time.perf_counter()
for _ in range(size // blk):
f.write(data)
f.flush()
seq_ms = (time.perf_counter() - t0) * 1000
with open(p, "r+b") as f:
t0 = time.perf_counter()
for pos in poses:
f.seek(pos)
f.write(data)
f.flush()
rand_ms = (time.perf_counter() - t0) * 1000
实跑输出:
--------------------------------------------------------------------
写入文件的实验——顺序写和随机写差在哪
--------------------------------------------------------------------
① 顺序写 4096 次 4 KB vs 随机写 2000 次 4 KB
走法 写了几次 写多少 耗时
----------------------------------------------
顺序写 4096 次 16 MB 17.21 毫秒
随机写 2000 次 7 MB 20.47 毫秒
**随机写只写了 7 MB,耗时却是顺序写 16 MB 的 1.2 倍。**
**按【每 MB 花多少时间】算,随机写贵了 2 倍。**
② 写比读更怕随机
原因 说明
---------------------------------------------------------------
要改的是【已有的页】 写之前可能得先把那一页读进来
改完还得写回去 一次随机写可能变成【一读一写】
文件系统要记元数据 块的分配表也得跟着改
设备的写放大 SSD 按块擦写,改 4 KB 可能动了 256 KB
**所以【写】的随机比【读】的随机更贵**——
**这也是为什么日志、数据库都尽量【追加写】,而不是【原地改】。**
③ 一个立刻能用的结论
**写文件时尽量不要【跳着写】**:**先攒在内存里,最后一次性顺序写出。**
**追加(append)永远比原地改快**——**因为它天生是顺序的。**
**这条规则在 SSD 上依然成立**(写放大),**在机械盘上是十倍以上的差距。**
顺序写 16 MB 用 12.99 毫秒;随机写 7 MB 用 21.95 毫秒。 按每 MB 算,随机贵 3 倍。注意这还是【没有 fsync】的数字——只是写到页缓存。 如果每次随机写都 fsync,差距会到几十倍——因为每次都要真的跳一次设备。 所以最差的组合是【小块 + 随机 + 每次 fsync】:三个乘法叠起来。 反过来最好的组合是【大块 + 顺序 + 攒一批 fsync】——这就是日志与批处理的写法。
📋 Linux 上看写放大与写量:cat /proc/<pid>/io 的 write_bytes(真到设备的字节数)与 wchar(进程写了多少)——两者差得越大,说明写放大越严重。
30 秒实验
cat /proc/self/io,对比 wchar 与 write_bytes。- 把随机写改成【先排序偏移量再写】,看耗时降多少。
- 用【写临时文件 + rename】改一次文件中间内容,与直接 seek 改对比。
常见误区
- 【写比读快】。写要处理元数据与可能的读回,随机的写比随机的读更贵。
- 【SSD 随便随机写】。SSD 有写放大:改 4 KB 可能动了 256 KB,性能与寿命双输。
- 【追加写是土办法】。它同时省了定位、读回、元数据三笔账,是所有日志系统的标准做法。
自测三问(点开对答案)
1. 随机写比顺序写贵在哪? —— **可能要先读回那一页、改完写回去、还要改元数据**,**SSD 上再加写放大。** 2. 为什么日志都用追加写? —— **追加天生顺序,不用定位、不用读回、元数据只改文件大小一个字段。** 3. 要改文件中间的内容,最稳的做法是什么? —— **写一个新文件再 rename 替换**——**全程顺序写,rename 是原子的。**- ☐ 6.13 调优参数
相关笔记
- 读取文件的实验 — 读的那一侧
- 调优参数 — 脏页什么时候回写
- 日志 — 追加写在文件系统里的样子
- 🐧 Linux是怎样工作的 · 索引