Linux 页面缓存命中率

最近的 Linux 性能下降是由页面缓存命中率的差异引起的:在旧系统上缓存得很好,而在新系统上缓存很差。那么如何直接测量页面缓存命中率呢?

像这样的工具怎么样?:

./cachestat 1

计算缓存函数…每 1 秒输出一次。
命中未命中 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

计算缓存函数…每 1 秒输出一次。
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;睡觉 2;总和 80m;睡觉 2;总和 80m

在 8:28:58,第一个命令删除了页面缓存,这可以从“CACHE_MB”(页面缓存大小)的大小从 191 MB 下降到 8 中看出。

休眠 2 秒后,在 8:29:01 发出 cksum 命令,针对 80 MB 文件(称为“80m”),导致总共约 20,400 次未命中(“MISSES”列)和页面缓存大小增长 80 MB。每页为 4 KB,因此 20k x 4k == 80 MB。未缓存读取期间的命中率下降到 3.6%。

最后,再休眠 2 秒后,在 8: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% 左右。如果您对缓存进行压力测试,则更高。它还使用内核函数的动态跟踪,这可能会导致内核冻结或崩溃,具体取决于您的内核版本。使用前进行测试。

统计数据也应被视为尽力而为。根据异常工作负载类型的频率,可能会有一些误差幅度,这四个内核函数不能正确匹配。使用已知工作负载进行测试,以确保它适用于预期目标。

问题
当我遇到较早的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) 并研究次要和主要故障。我认为这行不通(例如,常规 I/O)。

D) 使用 cache-hit-rate.stp SystemTap 脚本,它在互联网搜索 Linux 页面缓存命中率中排名第二。它在堆栈中的 VFS 接口中检测高速缓存访​​问,以便可以看到对任何文件系统或存储设备的读取。缓存未命中是通过其磁盘 I/O 测量的。这也遗漏了一些工作负载类型(该页面的“课程”中提到了一些),并将比率称为“比率”。

我本来会自己尝试 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
=> __面包
=> 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 工具 (perf-tools) 使用 ftrace。

我的首选技术是修改内核以检测页面缓存活动。例如,要么:

E) 应用 Keiichi 的 pagecache 监控内核补丁,它为缓存检测提供跟踪点,以及具有强大功能的工具:不仅是系统范围的比率,还包括每个进程和每个文件。我希望这是在主线。

F) 开发另一个内核补丁以将缓存命中/未命中统计信息添加到 /proc/meminfo。

然后,还有我自己用来解决这个问题的方法:使用 ftrace 和 perf_events 动态跟踪文件系统和磁盘 I/O 函数。

pcstat
如果您对页面缓存活动感兴趣,您还应该喜欢 Al Tobey 的 pcstat,它使用 mincore(或 fincore)来查看页面缓存中存在多少文件。这真是太棒了。

结论
希望将来内核将提供一种简单的方法来测量页面缓存活动,无论是来自 /proc 还是跟踪点。同时,我有适用于我的内核版本的 cachestat。它目前的实现很脆弱,如果不修改,可能无法在其他版本上很好地工作,所以它的最大价值可能是展示了一点点努力就能完成的事情。

转载翻译自: https://www.brendangregg.com