• 性能分析(7)- 未利用系统缓存导致 I/O 缓慢案例


    性能分析小案例系列,可以通过下面链接查看哦

    https://www.cnblogs.com/poloyy/category/1814570.html

    前提

    前面有学到 Buffer 和 Cache 的概念:https://www.cnblogs.com/poloyy/p/13503848.html

    我们来简单复习下

    Buffer 和 Cache 的设计目的

    为了提升系统的 I/O 性能,它们利用内存,充当起慢速磁盘和快速 CPU 之间的桥梁,可以加速 I/O 的访问速度

    Buffer 和 Cache

    • BUffer:对磁盘的读写数据缓存
    • Cache:对文件系统的读写数据缓存

    读写角度

    • 的角度来说,不仅可以优化磁盘和文件的写入,对应用程序也有好处,应用程序可以在数据真正落盘前,就返回去做其他工作
    • 的角度来说,不仅可以提高那些频繁访问数据的读取速度,也可以降低频繁 I/O 对磁盘的压力

    引入主题

    既然 Buffer 和 Cache 对系统性能有很大影响,那我们在软件开发的过程中,能不能利用这 一点,来优化 I/O 性能,提升应用程序的运行效率呢? 答案自然是肯定的

    缓存命中率

    灵魂拷问

    我们想利用缓存来提升程序的运行效率,应该怎么评估这个效果呢?换句话说,有没有哪个指标可以衡量缓存使用的好坏呢?

    灵魂回答

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

    缓存的重要性

    • 缓存是现在所有高并发系统必需的核心模块
    • 主要作用:把经常访问的数据(热点数据),提前读入到内存中,下次访问时就可以直接从内存读取数据,而不需要经过硬盘,从而加快应用程序的响应速度

    cachestat、cachetop

    独立的缓存模块通常会提供查询接口,方便我们随时查看缓存的命中情况

    不过 Linux 系统中并没有直接提供这些接口,所以这里要介绍一下,cachestat 和 cachetop ,它们正是查看系统缓存命中情况的工具

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

    Ubuntu 安装工具

    sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4052245BD4284CDD
    echo "deb https://repo.iovisor.org/apt/xenial xenial main" | sudo tee /etc/apt/sources.list.d/iovisor.list
    sudo apt-get update
    sudo apt-get install -y bcc-tools libbcc-examples linux-headers-$(uname -r)

    配置环境变量

    vim /etc/profile
    
    # 在文件结尾处添加
    export PATH=$PATH:/usr/share/bcc/tools
    
    # 保存文件后
    source /etc/profile

    cachestat

    # 它以 1 秒的时间间隔,输出了 3 组缓存统计数据
    cachestat 1 3

    字段说明

    cachetop

    cachetop

    • 默认按照缓存的命中次数(HITS)排序,展示了每个进程的缓存命中情况
    • 具体到每一个指标,这里的 HITS、MISSES 和 DIRTIES ,跟 cachestat 里的含义 一样,分别代表间隔时间内的缓存命中次数、未命中次数以及新增到缓存中的脏页数
    • READ_HIT:读的缓存命中率
    • WRITE_HIT:写的缓存命中率

    查看文件的缓存大小

    可以使用 pcstat 这个工具,来查看文件在内存中的缓存大小以及缓存比例

    安装 pcstat

    # 安装 go
    apt install golang-go
    
    vim /etc/profile
    
    # 在文件结尾处添加
    export GOPATH=~/go
    export PATH=~/go/bin:$PATH
    
    # 保存文件后
    source /etc/profile
     
    go get golang.org/x/sys/unix
    go get github.com/tobert/pcstat/pcstat

    运行 pcstat

    pcstat /bin/ls

    Cached 就是 /bin/ls 在缓存中的大小,而 Percent 则是缓存的百分比,看到它们都是 0,这说明 /bin/ls 并不在缓存中

    执行 ls 命令,再来查看

    ls
    pcstat /bin/ls

    发现都在缓存中了

    讲案例前的准备

    • 系统:Ubuntu 18.04,当然同样适用于其他的 Linux 系 统。
    • 机器配置:2 CPU,2 GB 内存
    • 预先按照上面的步骤安装 bcc 和 pcstat 软件包,并把这些工具的安装路径添加到到 PATH 环境变量中
    • 预先安装 Docker 软件包: apt-get install docker.io 
    • 打开两个终端同时连到 Linux 上

    案例一:通过 dd 写入读取文件

    注意:没说第几个终端都是默认第一个终端执行命令哦

     dd 命令生成一个临时文件

    # 生成一个 512MB 的临时文件
    dd if=/dev/sda1 of=file bs=1M count=512
    
    # 清理缓存
    echo 3 > /proc/sys/vm/drop_caches

    运行 pcstat 查看 file 文件

    pcstat file

    确认刚刚生成的文件不在缓存中。如果一切正常, 会看到 Cached 和 Percent 都是 0

    运行 cachetop 

    # 每隔 1 秒刷新一次数据
    cachetop 1

    第二个终端运行 dd 命令

    dd if=file of=/dev/null bs=1M

    • 从 dd 的结果可以看出,这个文件的读性能是 639 MB/s
    • 由于在 dd 命令运行前我们已经清理了缓存,所以 dd 命令读取数据时,肯定要通过文件系统从磁盘中读取

    查看 cachetop

    可以看到 dd 命令并不是所有的读都落到了磁盘上,读请求的缓存命中率只有 50%

    第二个终端再次运行 dd 命令

    dd if=file of=/dev/null bs=1M

    磁盘的读性能蹭蹭蹭往上涨,去到了 1.6GB/s

    再查看 cachetop

    可以发现,这次读的缓存命中率是 100%

    第二个终端运行 pcstat

    pcstat  file

    测试文件已经被全部缓存起来了,和刚刚 cachetop 观察到缓存命中率 100% 是一致的

    总结

    这两次结果说明,系统缓存对第二次 dd 操作有明显的加速效果,可以大大提高文件读取的性能。

    案例二

    前提

    • 这里运行了一个不可中断状态的进程
    • 功能:是每秒从磁盘分区 /dev/sda1 中读取 32MB 的数据, 并打印出读取数据花费的时间

    查看应用日志

    从这里可以看到,每读取 32 MB 的数据,就需要花 0.9 秒

    灵魂拷问

    这个时间合理吗?这也太慢了吧,那这是不是没用系统缓存导致的呢?

    查看 cachetop

    结果分析

    读的命中率虽然是 100%,命中次数是 1024,看起来全部的读请求都经过了系统缓存

    灵魂又拷问了

    全都是缓存 I/O,读取速度不应该这么慢

    深入分析

    • 另一个重要因素,每秒实际读取的数据大小,HITS 代表缓存的命中次数,那么每次命中能读取多少数据呢?自然是一页
    • 内存以页为单位进行管理,而每个页的大小是 4KB
    • 所以,在 5 秒的时间间隔 里,命中的缓存为 1024*4K/1024 = 4MB,再除以 5 秒,可以得到每秒读的缓存是 0.8MB,显然跟案例应用的 32 MB/s 相差太多

    结果猜想

    这个案例估计没有充分利用系统缓存,如果系统调用设置直接 I/O 的标志,就可以绕过系统缓存

    通过 strace 观察系统调用

    strace -p $(pgrep app)

    结果分析

    • 从 strace 的结果可以看到,案例应用调用了 openat 来打开磁盘分区 /dev/sdb1,并且传 入的参数为 O_RDONLY|O_DIRECT(中间的竖线表示或)
    • O_RDONLY 表示以只读方式打开
    • 而 O_DIRECT 则表示以直接读取的方式打开,这会绕过系统的缓存
    • 验证了这一点,就很容易理解为什么读 32 MB 的数据就都要那么久了
    • 直接从磁盘读写的速度,自然远慢于对缓存的读写,这也是缓存存在的最大意义了

    查看应用源码

    它果然用了直接 I/O

    修改源代码

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

    验证修复后的应用

    查看新应用的日志

    结果分析

    现在,每次只需要 0.03 秒,就可以读取 32MB 数据,明显比之前的 0.9 秒快多了。所以,这次应该用了系统缓存

    再次查看 cachetop

    结果分析

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

    总结

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

    扩展

    为什么优化前,通过 cachetop 只能看到很少一部分数据的全部命中,而没有观察到大量数据的未命中情况呢?

    回答

    是因为,cachetop 工具并不把直接 I/O 算进来

  • 相关阅读:
    ruby 二进制转十进制 Integer("0b101") = 5
    开始菜单和我的文档的我的图片及我的音乐变成 my pictrues 正常图标了
    ruby watir 莫名其妙的错误
    Excel SaveAS是去掉提示框
    apache && jboss安装
    ruby require include的区别
    ruby控制鼠标
    This error is raised because the column 'type' is reserved for storing the class in case of inheritance
    用正则表达式限制文本框只能输入数字,小数点,英文字母,汉字等各类代码
    ASP.NET 如何动态修改 Header 属性如添加 Meta 标签 keywords description!
  • 原文地址:https://www.cnblogs.com/poloyy/p/13519771.html
Copyright © 2020-2023  润新知