在 Linux 系统中,页缓存(Page Cache) 是影响文件 I/O 性能最关键、也最容易被误解的机制之一。
很多人在使用 dd 测试磁盘性能时,看到"几 GB/s"的结果欣喜若狂,却忽略了一个事实:你测到的可能并不是磁盘,而是内存。
本文通过实验结合 **dd、pcstat 以及 bcc 工具集(cachestat、cachetop)**来测试页缓存对文件读写性能的真实影响。
一、Linux 页缓存:文件 I/O 的"加速器"
Linux 在进行文件 I/O 时,默认会使用页缓存:
读文件:第一次读取 → 磁盘 I/O,后续读取 → 直接从内存命中
写文件:默认先写入页缓存(脏页),再由内核异步刷盘
这意味着文件系统性能 ≠ 磁盘性能很多时候,看到的是"缓存性能"。
二、工具介绍
1、pcstat:查看指定文件在内核页缓存中的状态
root@panda:~# pcstat /bin/ls
+---------+----------------+------------+-----------+---------+
| Name | Size (bytes) | Pages | Cached | Percent |
|---------+----------------+------------+-----------+---------|
| /bin/ls | 126584 | 31 | 31 | 100.000 |
+---------+----------------+------------+-----------+---------+
Cached 就是 /bin/ls 在缓存中的大小,而 Percent 则是缓存的百分比。如果是 0,这说明 /bin/ls 并不在缓存中。
2、cachestat(bc):提供了整个操作系统缓存的读写命中情况
cachestat 1 20
关键指标:
● TOTAL ,表示总的 I/O 次数;
● MISSES ,表示缓存未命中的次数;
● HITS ,表示缓存命中的次数;
● DIRTIES, 表示新增到缓存中的脏页数;
● BUFFERS_MB 表示 Buffers 的大小,以 MB 为单位;
● CACHED_MB 表示 Cache 的大小,以 MB 为单位。
3、cachetop(bc):提供了每个进程的缓存命中情况
默认按照缓存的命中次数(HITS)排序,展示了每个进程的缓存命中情况。具体到每一个指标,
这里的 HITS、MISSES和DIRTIES ,跟 cachestat 里的含义一样,分别代表间隔时间内的缓存命中次数、未命中次数以及新增到缓存中的脏页数。而 READ_HIT 和 WRITE_HIT ,分别表示读和写的缓存命中率。类似 top可看到,哪个进程命中最多缓存,谁在制造大量 MISS,读写命中率(READ_HIT / WRITE_HIT)非常适合定位I/O 抖动、缓存争抢、性能异常进程
二、读场景:页缓存对读取性能的巨大提升
构造测试文件,先从磁盘读取一个 512MB 文件:
root@panda:~# dd if=/dev/sda1 of=file bs=1M count=512
512+0 records in
512+0 records out
536870912 bytes (537 MB, 512 MiB) copied, 2.853 s, 188 MB/s
输出结果:536870912 bytes (537 MB, 512 MiB) copied, 2.853 s, 188 MB/s,这是典型的磁盘读取速度。
清理页缓存,模拟"首次访问"
root@panda:~# echo 3 > /proc/sys/vm/drop_caches
查看文件缓存状态:
root@panda:~# pcstat file
+-------+----------------+------------+-----------+---------+
| Name | Size (bytes) | Pages | Cached | Percent |
|-------+----------------+------------+-----------+---------|
| file | 536870912 | 131072 | 0 | 0.000 |
+-------+----------------+------------+-----------+---------+
说明文件完全不在页缓存中。
观察指标
第一个终端,每隔5秒刷新一次数据
cachetop 5
第一次读取文件(磁盘 I/O)
第二个终端
root@panda:~# dd if=file of=/dev/null bs=1M
512+0 records in
512+0 records out
536870912 bytes (537 MB, 512 MiB) copied, 0.583242 s, 920 MB/s
速度已经有所提升(顺序读 + 预读)。再次执行刚才的 dd 命令
第二次、第三次读取(缓存命中)
root@panda:~# dd if=file of=/dev/null bs=1M
512+0 records in
512+0 records out
536870912 bytes (537 MB, 512 MiB) copied, 0.118244 s, 4.5 GB/s <<<<<
root@panda:~# dd if=file of=/dev/null bs=1M
512+0 records in
512+0 records out
536870912 bytes (537 MB, 512 MiB) copied, 0.0774348 s, 6.9 GB/s
root@panda:~# dd if=file of=/dev/null bs=1M
512+0 records in
512+0 records out
536870912 bytes (537 MB, 512 MiB) copied, 0.0791728 s, 6.8 GB/s
root@panda:~# dd if=file of=/dev/null bs=1M
512+0 records in
512+0 records out
536870912 bytes (537 MB, 512 MiB) copied, 0.0791668 s, 6.8 GB/s
关键结论 :文件一旦完全进入页缓存,读取性能可以从百 MB/s → 数 GB/s
再次确认缓存状态:
root@panda:~# pcstat file
+-------+----------------+------------+-----------+---------+
| Name | Size (bytes) | Pages | Cached | Percent |
|-------+----------------+------------+-----------+---------|
| file | 536870912 | 131072 | 131072 | 100.000 |
+-------+----------------+------------+-----------+---------+
结论一:页缓存显著提高文件读取性能
第一次读受磁盘 I/O 限制,后续读完全命中页缓存,速度接近内存带宽,dd 读测试极易被缓存"欺骗"。
三、写入场景:同步写入 vs 缓存写入
同步写入(oflag=dsync)
root@panda:~# dd if=/dev/zero of=file bs=32k count=20000 oflag=dsync
^C794+0 records in
794+0 records out
26017792 bytes (26 MB, 25 MiB) copied, 7.43446 s, 3.5 MB/s
每次写入都需要等待数据真正落盘,没有写合并和异步刷盘,性能极低,但**非常真实,**可以用来模拟数据库commit落盘时真实的写入性能。
同步写入后,页缓存是否存在?
root@panda:~# pcstat test
+-------+----------------+------------+-----------+---------+
| Name | Size (bytes) | Pages | Cached | Percent |
|-------+----------------+------------+-----------+---------|
| test | 33751040 | 8240 | 8240 | 100.000 |
+-------+----------------+------------+-----------+---------+
说明即使是 dsync,文件内容仍然会进入页缓存,只是写入路径变慢。
普通写入(默认缓存写)
去掉 oflag=dsync 直接IO
root@panda:~# dd if=/dev/zero of=test bs=32k count=20000
20000+0 records in
20000+0 records out
655360000 bytes (655 MB, 625 MiB) copied, 0.846184 s, 774 MB/s
root@panda:~#
root@panda:~# dd if=/dev/zero of=file bs=32k count=20000
20000+0 records in
20000+0 records out
655360000 bytes (655 MB, 625 MiB) copied, 0.857168 s, 765 MB/s
这是典型的写入页缓存,异步刷盘,测到的是内存写性能
结论二:同步写入显著降低写性能
oflag=dsync / sync:写入真实磁盘性能,吞吐量明显下降
默认写入:极高吞吐,并不等价于磁盘能力
四、缓存让性能测试"严重失真"
很多人用:
dd if=/dev/zero of=file
dd if=file of=/dev/null
来测试磁盘性能,但如果不清缓存,不使用 O_DIRECT,那么测试的结果本质是:内存 → 内存
正确测试磁盘性能的方式:
1、清理缓存
echo 3 > /proc/sys/vm/drop_caches
2、或绕过页缓存
oflag=direct / iflag=direct
3、测试数据库commit落盘写入性能
oflag=dsync
五、实验总结
1、页缓存显著提高文件读取性能
文件第一次读取时,由于磁盘 I/O 限制,速度相对较慢。文件再次读取时,由于页缓存命中,速度可提升数倍至数十倍(甚至达到 GB/s 级别),大大提高文件访问效率。但同时也要注意,如果我们把 dd 当成测试文件系统性能的工具,由于缓存的存在,就会导致测试结果严重失真。
2、I/O 性能测试需谨慎,同步写入(dsync/sync)会降低写入速度
每次写入直接落盘会明显降低写入吞吐,但缓存仍可用于后续读取加速,IO慢可以查找是否是直接IO问题或者用dd直接IO模拟真实磁盘读写性能。缓存会导致 dd 或其他工具测试文件系统性能时出现严重失真。若想测试磁盘实际性能,缓存清理或使用 oflag=direct/O_DIRECT 来绕过页缓存。
3、清理缓存会恢复文件到未缓存状态
echo 3 > /proc/sys/vm/drop_caches 可清空页缓存,模拟首次磁盘访问场景。
4、缓存状态可通过工具实时监控
pcstat:查看指定文件在内核页缓存中的状态
cachestat(bc):提供了整个操作系统缓存的读写命中情况
cachetop(bc):提供了每个进程的缓存命中情况