《Linux性能优化实战》笔记(十)—— 系统缓存命中率

一、 查看工具简介

Linux系统中并没有直接提供查看系统缓存命中率的工具,所以这里我要介绍一下,cachestat 和 cachetop。

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

这两个工具都是 bcc 软件包的一部分,它们基于 Linux 内核的 eBPF(extended Berkeley Packet Filters)机制,来跟踪内核中管理的缓存,并输出缓存的使用和命中情况。

除了缓存的命中率外,还有一个指标你可能也会很感兴趣,那就是指定文件在内存中的缓存大小,可以使用pcstat 这个工具来查看。
 

二、 工具安装及输出介绍

1. Centos7安装bcc-tools

bcc-tools需要内核版本为4.1或者更新的版本,如果你用的是CentOS,那就需要手动升级内核版本后再安装。

升级内核版本

#升级系统
yum update -y

#安装ELRepo
rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
rpm -Uvh https://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm

#安装新内核
yum remove -y kernel-headers kernel-tools kernel-tools-libs
yum --enablerepo="elrepo-kernel" install -y kernel-ml kernel-ml-devel kernel-ml-headers kernel-ml-tools kernel-ml-tools-libs kernel-ml-tools-libs-devel

#更新Grub后重启
grub2-mkconfig -o /boot/grub2/grub.cfg
grub2-set-default 0
reboot

#重启后确认内核版本已升级为4.20.0-1.el7.elrepo.x86_64
uname -r

安装 bcc-tools

#安装bcc-tools
yum install -y bcc-tools
#配置PATH路径
export PATH=$PATH:/usr/share/bcc/tools
#验证安装成功
cachestat

2. 安装pcstat

pcstat 是一个基于 Go 语言开发的工具,所以安装它之前首先应该安装 Go 语言。

wget https://studygolang.com/dl/golang/go1.13.4.linux-amd64.tar.gz  #下载
gunzip go1.13.4.linux-amd64.tar.gz  #解压
tar -xvf go1.13.4.linux-amd64.tar  #解压
mv go /usr/local/  
echo 'export PATH=$PATH:/usr/local/go/bin'>>/etc/profile  #配置系统变量
go --version  #查看版本。正常显示则表明安装正常

安装完 Go 语言,再运行下面的命令安装 pcstat:

$ export GOPATH=~/go
$ export PATH=~/go/bin:$PATH
$ go get golang.org/x/sys/unix
$ go get github.com/tobert/pcstat/pcstat

 

3. 输出简介

cachestat

cachestat的输出是一个表格。每行代表一组数据,而每一列代表不同的缓存统计指标。

《《Linux性能优化实战》笔记(十)—— 系统缓存命中率》

  • TOTAL ,表示总的 I/O 次数;
  • MISSES ,表示缓存未命中的次数;
  • HITS ,表示缓存命中的次数;
  • DIRTIES, 表示新增到缓存中的脏页数;
  • BUFFERS_MB 表示 Buffers 的大小,以 MB 为单位;
  • CACHED_MB 表示 Cache 的大小,以 MB 为单位。

 

cachetop

cachetop的输出跟 top 类似,默认按照缓存的命中次数(HITS)排序,展示每个进程的缓存命中情况。

《《Linux性能优化实战》笔记(十)—— 系统缓存命中率》

HITS、MISSES 和 DIRTIES 跟 cachestat 里的含义一样,分别代表间隔时间内的缓存命中次数、未命中次数以及新增到缓存中的脏页数。READ_HIT 和 WRITE_HIT 分别表示读和写的缓存命中率。

 

pcstat

下面这个输出中,Cached 就是 /bin/ls 在缓存中的大小,Percent 则是缓存的百分比。目前它们都是 0,说明 /bin/ls 并不在缓存中。

《《Linux性能优化实战》笔记(十)—— 系统缓存命中率》

如果你执行一下 ls 命令,再运行相同的命令来查看的话,就会发现 /bin/ls 都在缓存中了:

《《Linux性能优化实战》笔记(十)—— 系统缓存命中率》

 

三、 缓存优化案例模拟

1. dd多次读取文件

dd 作为一个磁盘和文件的拷贝工具,经常被拿来测试磁盘或者文件系统的读写性能。不过,既然缓存会影响到性能,如果用 dd 对同一个文件进行多次读取测试,测试的结果会怎么样呢?

首先生成待读取文件并清理缓存,确认它不在缓存中

# 生成一个512MB的临时文件
$ dd if=/dev/sda1 of=file bs=1M count=512
# 清理缓存
$ echo 3 > /proc/sys/vm/drop_caches
# 确认刚刚生成的文件不在缓存中。如果一切正常,你会看到 Cached 和 Percent 都是 0
$ pcstat file
+-------+----------------+------------+-----------+---------+
| Name | Size (bytes) | Pages | Cached | Percent |
|-------+----------------+------------+-----------+---------|
| file | 536870912 | 131072 | 0 | 000.000 |
+-------+----------------+------------+-----------+---------+

在第一个终端中,现在运行 cachetop 命令

# 每隔5秒刷新一次数据
$ cachetop 5

第二个终端,运行 dd 命令测试文件的读取速度

$ dd if=file of=/dev/null bs=1M
512+0 records in
512+0 records out
536870912 bytes (537 MB, 512 MiB) copied, 16.0509 s, 33.4 MB/s

这个文件的读性能是 33.4 MB/s。由于在 dd 命令运行前我们已经清理了缓存,所以 dd 命令读取数据时,肯定要通过文件系统从磁盘中读取。不过,这是不是意味着, dd 所有的读请求都能直接发送到磁盘呢?

我们再回到第一个终端, 查看 cachetop 界面的缓存命中情况:可以发现,并不是所有的读都落到了磁盘上,事实上读请求的缓存命中率只有 50% 。

PID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_
\.\.\.
3264 root dd 37077 37330 0 49.8% 

切换到第二个终端,再次执行刚才的 dd 命令:

$ dd if=file of=/dev/null bs=1M
512+0 records in
512+0 records out
536870912 bytes (537 MB, 512 MiB) copied, 0.118415 s, 4.5 GB/s

磁盘的读性能居然变成了 4.5 GB/s,比第一次的结果明显高了太多。为什么这次的结果这么好呢?不妨再回到第一个终端,看看 cachetop 的情况:可以发现,这次的读的缓存命中率是 100.0%,也就是说这次的 dd 命令全部命中了缓存,所以才会看到那么高的性能。

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

回到第二个终端,再次执行 pcstat 查看文件 file 的缓存情况:

pcstat file
+-------+----------------+------------+-----------+---------+
| Name | Size (bytes) | Pages | Cached | Percent |
|-------+----------------+------------+-----------+---------|
| file | 536870912 | 131072 | 131072 | 100.000 |
+-------+----------------+------------+-----------+---------+

从 pcstat 的结果你可以发现,测试文件 file 已经被全部缓存了起来,这跟刚才观察到的缓存命中率 100% 是一致的。
这两次结果说明,系统缓存对第二次 dd 操作有明显的加速效果,可以大大提高文件读取的性能。同时也要注意,如果我们把 dd 当成测试文件系统性能的工具,由于缓存的存在,就会导致测试结果严重失真。
 

2. 直接IO案例

这个案例类似于前面学过的不可中断状态进程的例子。它的基本功能比较简单,也就是每秒从磁盘分区 /dev/sda1 中读取 32MB 的数据,并打印出读取数据花费的时间。

从应用日志中我们发现读数据很慢,读32M数据需要大概1s

docker logs app
Reading data from disk /dev/sdb1 with buffer size 33554432
Time used: 0.929935 s to read 33554432 bytes
Time used: 0.949625 s to read 33554432 bytes

先看看 cachetop 的输出,找到案例进程 app 的缓存使用情况:

《《Linux性能优化实战》笔记(十)—— 系统缓存命中率》

乍一看,1024 次缓存全部命中,读的命中率是 100%,看起来全部的读请求都经过了系统缓存。但是你注意,HITS 代表
缓存的命中次数,每次命中能读取多少数据呢?自然是一页。内存以页为单位进行管理,而每个页的大小是 4KB。所以,在 5 秒的时间间隔里,命中的缓存为 1024*4K/1024 = 4MB,再除以 5 秒,可以得到每秒读的缓存是0.8MB,显然跟案例应用的 32 MB/s 相差太多。

我们猜想,这个案例估计没有充分利用系统缓存。其实前面我们遇到过类似的问题,如果为系统调用设置直接 I/O 的标志,就可以绕过系统缓存。要判断应用程序是否用了直接 I/O,最简单的方法当然是观察它的系统调用,查找应用程序在调用它们时的选项。使用什么工具来观察系统调用呢?自然还是 strace。

运行下面的 strace 命令,观察案例应用的系统调用情况。注意,这里使用了 pgrep 命令来查找案例进程的 PID 号:

《《Linux性能优化实战》笔记(十)—— 系统缓存命中率》

可以看到,应用调用了 openat 来打开磁盘分区 /dev/sdb1,并且传入的参数为 O_RDONLY|O_DIRECT。|表示或,O_RDONLY 表示以只读方式打开,而 O_DIRECT 则表示以直接读取的方式打开,这会绕过系统的缓存。

再看看案例应用的源代码,再次验证一下:它果然用了直接 I/O。

《《Linux性能优化实战》笔记(十)—— 系统缓存命中率》

验证了这一点,就很容易理解为什么读 32 MB 的数据就都要那么久了。直接从磁盘读写的速度,自然远慢于对缓存的读写。这也是缓存存在的最大意义了。
 

修改源代码,删除O_DIRECT 选项,让应用程序使用缓存 I/O ,而不是直接 I/O,就可以加速磁盘读取。

再来运行下面的命令查看新应用的日志,每次只需要 0.03 秒,就可以读取 32MB 数据,所以,这次应该用了系统缓存。

《《Linux性能优化实战》笔记(十)—— 系统缓存命中率》

回到第一个终端,查看 cachetop 的输出来确认一下:

《《Linux性能优化实战》笔记(十)—— 系统缓存命中率》

读的命中率还是 100%,HITS (即命中数)却变成了 40960,同样的方法计算一下,换算成每秒字节数正好是 32 MB(即 40960*4k/5/1024=32M)。

这个案例说明,在进行 I/O 操作时,充分利用系统缓存可以极大地提升性能。 但在观察缓存命中率时,还要注意结合应用程序实际的 I/O 大小,综合分析缓存的使用情况。

案例的最后,再回到开始的问题,为什么优化前,通过 cachetop 只能看到很少一部分数据的全部命中,而没有观察到大量数据的未命中情况呢?这是因为,cachetop 工具并不把直接 I/O 算进来。这也又一次说明了,了解工具原理的重要。

    原文作者:Hehuyi_In
    原文地址: https://blog.csdn.net/Hehuyi_In/article/details/108678030
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞