undefined

缓存命中率

这里的缓存指的是数据在内存中的临时存储

缓存命中率:指直接通过缓存获取数据的请求次数,占所有数据请求次数的百分比。命中率越高,表示使用缓存带来的收益越高,应用程序的性能也就越好

一、工具介绍

1. cachestat 和 cachetop

  • cachestat:提供了整个操作系统缓存的读写命中情况
  • cachetop:提供了每个进程的缓存命中情况

这两个工具都是 bbc 软件包的一部分,基于 Linux 内核的 eBPF(extended Berkeley Packet Filters)机制,来跟踪内核中管理的缓存,并输出缓存的使用和命中情况。bbc-tools 需要内核为 4.1 或者更新的版本。

安装:这个库提供了编译好的二进制:https://github.com/brendangregg/perf-tools

cachestat 的使用,以 1 秒的时间间隔输出:

1
2
3
4
5
6
➜  [/tmp] cachestat 1
Counting cache functions... Output every 1 seconds.
HITS MISSES DIRTIES RATIO BUFFERS_MB CACHE_MB
2632 0 15 100.0% 964 8825
1396 0 11 100.0% 964 8825
2228 0 17 100.0% 964 8825
  • HITS:表示缓存命中的次数
  • MISSES:表示缓存未命中的次数
  • DIRTIES:表示新增到缓存中的脏页数
  • RATIO:缓存命中率,表示 HITS / (HITS + MISSES) 的值
  • BUFFERS_MB:表示 Buffers 的大小,以 MB 为单位
  • CACHE_MB:表示 cache 的大小,以 MB 为单位

cachetop 的使用:

1
2
3
4
$ cachetop
11:58:50 Buffers MB: 258 / Cached MB: 347 / Sort: HITS / Order: ascending
PID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_HIT%
13029 root python 1 0 0 100.0% 0.0%

cachetop 默认会按照缓存的命中次数(HITS)排序,展示了每个进程的缓存命中情况。字段含义和 cachestat 一样;READ_HIT 和 WRITE_HIT 分别表示读和写的缓存命中率。

2. pcstat

安装( x86_64 系统): curl -L -o pcstat https://github.com/tobert/pcstat/raw/2014-05-02-01/pcstat.x86_64

作用:查看文件在内存中的缓存大小以及缓存比例

1
2
3
4
5
6
7
8
9
10
11
12
13
➜  [/usr/local] pcstat /bin/perf
|-----------+----------------+------------+-----------+---------|
| Name | Size | Pages | Cached | Percent |
|-----------+----------------+------------+-----------+---------|
| /bin/perf | 7068944 | 1726 | 0 | 000.000 |
|-----------+----------------+------------+-----------+---------|
➜ [/usr/local] perf top
➜ [/usr/local] pcstat /bin/perf
|-----------+----------------+------------+-----------+---------|
| Name | Size | Pages | Cached | Percent |
|-----------+----------------+------------+-----------+---------|
| /bin/perf | 7068944 | 1726 | 1726 | 100.000 |
|-----------+----------------+------------+-----------+---------|

如上,Cached 就是 /bin/perf 在缓存中的大小,Percent 则是缓存的百分比。如果为 0 表示不在缓存中;之后我们使用了 perf 命令,就会发现 perf 已经在缓存中

二、案例场景

1. 文件被缓存前和缓存后的读取速度

  • 使用 dd 命令生成一个临时文件

    1
    2
    3
    4
    # 生成一个512MB的临时文件
    $ dd if=/dev/sda1 of=file bs=1M count=512
    # 清理缓存
    $ echo 3 > /proc/sys/vm/drop_caches
  • 执行 pcstat ,确认刚刚生成的文件不在缓存中。

    1
    2
    3
    4
    5
    6
    ➜  [/tmp] pcstat file 
    |----------+----------------+------------+-----------+---------|
    | Name | Size | Pages | Cached | Percent |
    |----------+----------------+------------+-----------+---------|
    | file | 536870912 | 131072 | 0 | 000.000 |
    |----------+----------------+------------+-----------+---------|
  • 执行 cachetop 5 命令每 5 秒刷新一次数据

  • 在其他终端运行 dd 命令测试文件的读取速度。可以看到,这个文件的读取性能是 123 MB/s,由于 dd 命令运行之前我们清理了缓存,因此 dd 命令在读取数据时,肯定要通过文件系统从磁盘中读取。

    1
    2
    3
    4
    ➜  [/tmp] dd if=file of=/dev/null bs=1M
    512+0 records in
    512+0 records out
    536870912 bytes (537 MB) copied, 4.37107 s, 123 MB/s
  • 回去看一下 cachetop 的终端,查看进程缓存命中情况。并不是所有的读都落到了磁盘,读请求的缓存命中率只有 50%

    1
    2
    3
    PID      UID      CMD              HITS     MISSES   DIRTIES  READ_HIT%  WRITE_HIT%
    \.\.\.
    3264 root dd 37077 37330 0 49.8% 50.2%
  • 此时看一下 pcstat file ,这个文件已经全部缓存在内存中

    1
    2
    3
    4
    5
    6
    ➜  [/tmp] pcstat file 
    |----------+----------------+------------+-----------+---------|
    | Name | Size | Pages | Cached | Percent |
    |----------+----------------+------------+-----------+---------|
    | file | 536870912 | 131072 | 131072 | 100.000 |
    |----------+----------------+------------+-----------+---------|
  • 再次执行 dd 命令,可以看到读性能变成了 6.6 GB/s 。说明是从缓存中读取的。

    1
    2
    3
    4
    ➜  [/tmp] dd if=file of=/dev/null bs=1M
    512+0 records in
    512+0 records out
    536870912 bytes (537 MB) copied, 0.0808783 s, 6.6 GB/s
  • 在看一下 cachetop 的终端,读的缓存命中率是 100%,也就是说这次的 dd 命令全部命中了缓存,所以性能特别高

    1
    2
    3
    4
    10:45:22 Buffers MB: 4 / Cached MB: 719 / Sort: HITS / Order: ascending
    PID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_HIT%
    \.\.\.
    32642 root dd 131637 0 0 100.0% 0.0%

结论:系统缓存对第二次 dd 操作有明显的加速效果,可以大大提高文件读取的性能。但是同时也要注意如果用 dd 当成测试文件系统性能的工具,由于缓存的存在,会导致测试结果严重失真,每次 dd 的时候需要先清理缓存 echo 3 > /proc/sys/vm/drop_caches

2. 使用 direct IO

1
2
int flags = O_RDONLY | O_LARGEFILE | O_DIRECT; 
int fd = open(disk, flags, 0755);

如上打开文件的方式使用了直接 IO,这样会绕过系统的缓存。我们可以观察 cachetop 中某个进程命中的次数

1
2
3
16:39:18 Buffers MB: 73 / Cached MB: 281 / Sort: HITS / Order: ascending
PID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_HIT%
21881 root app 1024 0 0 100.0% 0.0%

如上所示,HITS 为 1024,说明缓存的命中次数为 1024,每次命中会读取一页,内存以页为单位进行管理,而每个页的大小是 4KB。假如整个读取过程是 5 秒,因此,在 5 秒的时间间隔里,命中的缓存为 1024 * 4K = 4MB ,再除 5 秒,可以得到每秒读的缓存是 0.8 MB。

接下来,可以使用 strace 来看下程序执行过程中系统调用来确认程序的那部分出现问题。

注意:cachetop 工具并不把直接 IO 算进来,因此如果使用直接 IO 的话,不会看到大量数据的未命中情况

注意:直接 IO 是跳过 buffer,裸 IO 是跳过文件系统(还是有 buffer 的)

注意:如上使用直接 IO,但还是有 100% 缓存命中率,因为还有元数据缓存