最近一次 Linux 性能回退,最终发现是页缓存命中率变化造成的:旧系统的缓存效果很好,新系统却很差。那么,如何直接测量页缓存命中率?
像下面这样的工具怎么样?
# ./cachestat 1
Counting cache functions... Output every 1 seconds.
HITS MISSES DIRTIES RATIO BUFFERS_MB CACHE_MB
210 869 0 19.5% 2 209
444 1413 0 23.9% 8 210
471 1399 0 25.2% 12 211
403 1507 3 21.1% 18 211
967 1853 3 34.3% 24 212
422 1397 0 23.2% 30 212
[...]
它不只显示缓冲区和页缓存的大小,也显示活动统计。我已将 cachestat 加入 GitHub 上的 perf-tools 工具集。
更完整的示例
下面先给出示例输出,再说明产生它的工作负载:
# ./cachestat -t
Counting cache functions... Output every 1 seconds.
TIME HITS MISSES DIRTIES RATIO BUFFERS_MB CACHE_MB
08:28:57 415 0 0 100.0% 1 191
08:28:58 411 0 0 100.0% 1 191
08:28:59 362 97 0 78.9% 0 8
08:29:00 411 0 0 100.0% 0 9
08:29:01 775 20489 0 3.6% 0 89
08:29:02 411 0 0 100.0% 0 89
08:29:03 6069 0 0 100.0% 0 89
08:29:04 15249 0 0 100.0% 0 89
08:29:05 411 0 0 100.0% 0 89
08:29:06 411 0 0 100.0% 0 89
08:29:07 411 0 3 100.0% 0 89
[...]
我使用 -t 选项加入 TIME 列,以便说明输出。工作负载为:
# echo 1 > /proc/sys/vm/drop_caches; sleep 2; cksum 80m; sleep 2; cksum 80m
在08:28:58,第一条命令清除了页缓存。CACHE_MB(页缓存大小)从191 MB 降为8 MB,体现了这一变化。
等待2秒后,在08:29:01对名为 80m 的80 MB 文件执行 cksum,总共导致约20,400次未命中(MISSES 列),页缓存增大80 MB。每页为4 KB,因此20k × 4k = 80 MB。未缓存读取期间,命中率降到3.6%。
再等待2秒后,08:29:03第二次执行 cksum。这次完全命中缓存,统计跨越了两行输出。
工作原理
我想知道 Linux 内核内置的 ftrace 能否测量缓存活动,因为 ftrace 函数性能分析能在内核中高效计数。系统的缓存活动频率可能很高,因此必须认真考虑任何插桩的开销。
ftrace 函数性能分析虽然开销较低,但能力也有限。它可以按 CPU 统计内核函数调用,并显示平均延迟。这与 perf-tools 中 funccount 使用的是同一机制。例如,我无法使用高级过滤器匹配函数参数或返回值。只有仅凭内核函数调用频率推断缓存活动时,这种方式才能奏效。
对于我研究的内核(3.2和3.13),我分析以下四个内核函数以测量缓存活动:
mark_page_accessed():测量缓存访问。mark_buffer_dirty():测量缓存写入。add_to_page_cache_lru():测量页面加入。account_page_dirtied():测量页面变脏。
mark_page_accessed() 显示总缓存访问次数,add_to_page_cache_lru() 显示缓存插入次数。add_to_page_cache_locked() 也能显示插入,甚至包含一个跟踪点,但在较新的内核上不会触发。
我一度以为这两个函数已经足够:假定插入就是未命中,那么已有未命中次数和总访问次数,便可算出命中次数。
问题在于,写入也会发生访问和插入,并将缓存数据标记为脏。因此另外两个函数帮助区分这些情况。请记住,我只能使用函数调用频率。mark_buffer_dirty() 用于识别哪些访问来自写入,account_page_dirtied() 用于识别哪些插入来自写入。
你的内核版本中,这些函数可能已经改名,或逻辑不同,脚本便无法原样工作。我希望少于四个函数就能实现,以提高可维护性,但在已测试的工作负载中,没有找到有效的更小函数集合。
如果 cachestat 在我使用的内核版本上过于频繁地失效,我可能会改写为使用支持过滤的 SystemTap 或 perf_events。
注意事项
缓存活动插桩确实有开销,该工具可能使目标系统慢约2%。如果对缓存进行压力测试,开销可能更高。它还使用内核函数动态跟踪,具体内核版本下可能导致内核冻结或 panic。使用前应先测试。
这些统计也应视为尽力而为的估计。某些不常见工作负载不能被这四个内核函数正确匹配,其出现频率可能带来误差。应用于目标场景前,应通过已知工作负载测试,建立可信度。
最初的问题
遇到前面提到的 Linux 性能回退时,我还没有 cachestat。我们发现磁盘 I/O 频率很高,于是我调查原因,逐步追溯到了缓存未命中。我使用自定义 ftrace 和 perf_events 命令,测量内核函数频率及其调用栈。
虽然完成了排查,但我希望下次有更好的方法,于是开发了 cachestat。
其他方法
我发现人们通常采用以下几种方法研究 Linux 页缓存命中率:
A) 使用 iostat(1) 监控磁盘读取,研究页缓存未命中频率,并假定这些读取来自缓存未命中,而不是例如 O_DIRECT。实际上,未命中频率通常比命中率更重要,因为未命中与应用的性能痛点成正比。同时使用 free(1) 查看缓存大小。
B) 清空页缓存(echo 1 > /proc/sys/vm/drop_caches),测量性能变差了多少!我很喜欢这种负向实验,但用它了解缓存使用情况,代价当然很高。
C) 使用 sar(1) 研究 minor fault 和 major fault。我认为这行不通,例如对于常规 I/O。
D) 使用 SystemTap 的 cache-hit-rate.stp 脚本。它在“Linux page cache hit ratio”的互联网搜索中排第二。脚本在调用栈较高的 VFS 接口处跟踪缓存访问,因此能看到任何文件系统或存储设备的读取;缓存未命中通过磁盘 I/O 测量。它也会遗漏部分工作负载类型(相关页面的 Lessons 一节提及了一些),而且把比值称为 rate。
原本我会先尝试 SystemTap 方法,但它可能遗漏 mmap 读取以及其他内核来源。例如,下方 mark_page_accessed()(一次缓存读取)的调用栈表明,我们是通过 write() 系统调用到达这里的:
dd-30425 [000] 6788093.150288: mark_page_accessed: (mark_page_accessed+0x0/0x60)
dd-30425 [000] 6788093.150291:
=> __getblk
=> __bread
=> ext3_get_branch
=> ext3_get_blocks_handle
=> ext3_get_block
=> __block_write_begin
=> ext3_write_begin
=> generic_perform_write
=> generic_file_buffered_write
=> __generic_file_aio_write
=> generic_file_aio_write
=> do_sync_write
=> vfs_write
=> sys_write
=> system_call_fastpath
这里读取的是文件系统元数据。示例通过我的 kprobe 工具使用 ftrace,工具属于 perf-tools。
我更倾向于修改内核,对页缓存活动插桩,例如选择:
E) 应用 Keiichi 的 pagecache monitoring 内核补丁。它提供缓存插桩跟踪点和功能很强的工具,不仅能得到系统级比值,还能按进程、按文件观察。我希望它进入主线。
F) 开发另一个内核补丁,向 /proc/meminfo 加入缓存命中、未命中统计。
此外,还有我实际排查该问题时采用的方法:通过 ftrace 和 perf_events 动态跟踪文件系统和磁盘 I/O 函数。
pcstat
如果你对页缓存活动感兴趣,也应该看看 Amy Tobey 的 pcstat。它使用 mincore(或 fincore),查看文件有多少内容存在于页缓存中,十分好用。
结论
希望未来内核能通过 /proc 或跟踪点,提供简单的页缓存活动测量方式。目前,我有适用于自己内核版本的 cachestat。它当前的实现比较脆弱,其他版本可能需要修改后才能正常工作,因此最大的价值也许是展示:投入一点努力,就可以实现什么。
原文:Linux Page Cache Hit Ratio
作者:Brendan Gregg,2014年12月31日。本文保留原文历史内核范围3.2/3.13;示例输出来自原文,不是本次实测。原文权利归原权利人所有,转载依据用户对本批文章的明确授权;工具自身许可证须发布前核对。











暂无评论内容