系统监控-硬件资源-CPU篇-整体思路-性能指标和工具-使用率、上下文切换、负载、命中率

系统监控-硬件资源-CPU篇-整体思路-性能指标和工具-使用率、上下文切换、负载、命中率
参考来源:

在系统监控的综合思路篇中,我曾经介绍过,系统资源的瓶颈,可以通过 USE 法,即使用
率、饱和度以及错误数这三类指标来衡量。系统的资源,可以分为硬件资源和软件资源两
类。

如 CPU、内存、磁盘和文件系统以及网络等,都是最常见的硬件资源

而文件描述符数、连接跟踪数、套接字缓冲区大小等,则是典型的软件资源

这样,在你收到监控系统告警时,就可以对照这些资源列表,再根据指标的不同来进行定位。

CPU 性能分析整体思路

第一种最常见的系统资源是 CPU。关于 CPU 的性能分析方法,我在如何迅速分析出系统
CPU 的瓶颈中,已经为你整理了一个迅速分析 CPU 性能瓶颈的思路。

利用 top、vmstat、pidstat、strace 以及 perf 等几个最常见的工具,
获取 CPU 性能指标后,再结合进程与 CPU 的工作原理,就可以迅速定位出 CPU 性能瓶颈。

在这里插入图片描述
这张图里,我列出了 top、vmstat 和 pidstat 分别提供的重要的 CPU 指标,并用虚线表示关联关系,对应出了性能分析下一步的方向。

通过这张图你可以发现,这三个命令,几乎包含了所有重要的 CPU 性能指标,比如:

从 top 的输出可以得到各种 CPU 使用率以及僵尸进程和平均负载等信息。

从 vmstat 的输出可以得到上下文切换次数、中断次数、运行状态和不可中断状态的进程数。

从 pidstat 的输出可以得到进程的用户 CPU 使用率、系统 CPU 使用率、以及自愿上下文切换和非自愿上下文切换情况。

实际上,top、pidstat、vmstat 这类工具所汇报的 CPU 性能指标,都源自 /proc 文件系统(比如 /proc/loadavg、/proc/stat、/proc/softirqs等)。这些指标,都应该通过监控系统监控起来。虽然并非所有指标都需要报警,但这些指标却可以快性能问题的定位分
析。
比如说,当你收到系统的用户 CPU 使用率过高告警时,从监控系统中直接查询到,导致CPU 使用率过高的进程;然后再登录到进程所在的 Linux 服务器中,分析该进程的行为。

你可以使用 strace,查看进程的系统调用汇总;也可以使用 perf 等工具,找出进程的热点函数;甚至还可以使用动态追踪的方法,来观察进程的当前执行过程,直到确定瓶颈的根源。

cpu性能指标和CPU性能工具速查表

在这里插入图片描述在这里插入图片描述

CPU性能指标01-CPU使用率(排查方向)

CPU 使用率是最直观和最常用的系统性能指标,更是我们在排查性能问题时,通常会关注的第一个指标。所以我们更要熟悉它的含义,尤其要弄清楚用户(%user)、Nice(%nice)、系统(%system) 、等待 I/O(%iowait) 、中断(%irq)以及软中断(%softirq)这几种不同 CPU 的使用率。比如说:

用户 CPU 和 Nice CPU 高,说明用户态进程占用了较多的 CPU,所以应该着重排查进程的性能问题

系统 CPU 高,说明内核态占用了较多的 CPU,所以应该着重排查内核线程或者系统调用的性能问题

I/O 等待 CPU 高,说明等待 I/O 的时间比较长,所以应该着重排查系统存储是不是出现了 I/O 问题

软中断和硬中断高,说明软中断或硬中断的处理程序占用了较多的 CPU,所以应该着重排查内核中的中断服务程序

碰到 CPU 使用率升高的问题,你可以借助 top、pidstat 等工具,确认引发 CPU 性能问题的来源;再使用 perf 等工具,排查出引起性能问题的具体函数。

CPU 使用率相关的重要指标

你还会在很多其他的性能工具中看到它们。下面,我来依次解读一下。

user(通常缩写为 us),代表用户态 CPU 时间。注意,它不包括下面的 nice 时间,但
包括了 guest 时间。
nice(通常缩写为 ni),代表低优先级用户态 CPU 时间,也就是进程的 nice 值被调整
为 1-19 之间时的 CPU 时间。这里注意,nice 可取值范围是 -20 到 19,数值越大,优
先级反而越低。

system(通常缩写为 sys),代表内核态 CPU 时间。
idle(通常缩写为 id),代表空闲时间。注意,它不包括等待 I/O 的时间(iowait)。
iowait(通常缩写为 wa),代表等待 I/O 的 CPU 时间。
irq(通常缩写为 hi),代表处理硬中断的 CPU 时间。
softirq(通常缩写为 si),代表处理软中断的 CPU 时间。
steal(通常缩写为 st),代表当系统运行在虚拟机中的时候,被其他虚拟机占用的 CPU
时间。
guest(通常缩写为 guest),代表通过虚拟化运行其他操作系统的时间,也就是运行虚
拟机的 CPU 时间。
guest_nice(通常缩写为 gnice),代表以低优先级运行虚拟机的时间。

CPU 使用率,就是除了空闲时间外的其他时间占总 CPU 时间的百分比,
在这里插入图片描述

这里我们以常用的 top 命令为例,来看看 CPU 更加细化的利用率指标(不同版本的 top
命令显示可能会略有不同):
%Cpu(s): 12.5 us, 0.0 sy, 0.0 ni, 87.4 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
top 命令显示了 us、sy、ni、id、wa、hi、si 和 st 这几个指标,这几个指标之和为
100。那你可能会有疑问,细化 CPU 利用率指标的监控会不会带来明显的额外开销?答案
是不会的,因为 CPU 利用率监控通常是去解析 /proc/stat 文件,而这些文件中就包含了
这些细化的指标。

我们继续来看下上述几个指标的具体含义,这些含义你也可以从top 手册里来查看:
在这里插入图片描述
上述指标的具体含义以及注意事项如下:
在这里插入图片描述

CPU性能指标02-上下文切换

在每个任务运行前,CPU 都需要知道任务从哪里加载、又从哪里开始运行,也就是说,需要系统事先帮它设置好 CPU 寄存器和程序计数器(Program Counter,PC)。

CPU 寄存器,是 CPU 内置的容量小、但速度极快的内存。而程序计数器,则是用来存储CPU 正在执行的指令位置、或者即将执行的下一条指令位置。它们都是 CPU 在运行任何任务前,必须的依赖环境,因此也被叫做 CPU 上下文。

CPU 上下文切换,就是先把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。

而这些保存下来的上下文,会存储在系统内核中,并在任务重新调度执行时再次加载进来。这样就能保证任务原来的状态不受影响,让任务看起来还是连续运行。

CPU 上下文切换无非就是更新了 CPU 寄存器的值嘛,但这些寄存器,本身就是为了快速运行任务而设计的,为什么会影响系统的 CPU 性能呢?

系统调用结束后,CPU 寄存器需要恢复原来保存的用户态 ,然后再切换到用户空间,继续运行进程。所以,一次系统调用的过程,其实是发生了两次 CPU 上下文切换。

进程上下文切换,是指从一个进程切换到另一个进程运行。而系统调用过程中一直是同一个进程在运行
所以,系统调用过程通常称为特权模式切换,而不是上下文切换。但实际上,系统调用过程中,CPU 的上下文切换还是无法避免的。
进程的上下文切换就比系统调用时多了一步:在保存当前进程的内核状态和 CPU 寄存器之前,需要先把该进程的虚拟内存、栈等保存下来;而加载了下一进程的内核态后,还需要刷新进程的虚拟内存和用户栈。
根据任务的不同,CPU 的上下文切换就可以分为几个不同的场景,也就是进程上下文切换、线程上下文切换以及中断上下文切换。
硬件通过触发信号,会导致中断处理程序的调用,也是一种常见的任务。

CPU上下文切换场景1–进程上下文切换(五种场景)

什么时候会切换进程上下文?
只有在进程调度的时候,才需要切换上下文。Linux 为每个 CPU 都维护了一个就绪队列,将活跃进程(即正在运行和正在等待 CPU 的进程)按照优先级和等待 CPU 的时间排序,然后选择最需要 CPU 的进程,也就是优先级最高和等待 CPU 时间最长的进程来运行。

那么,进程在什么时候才会被调度到 CPU 上运行呢?

最容易想到的一个时机,就是进程执行完终止了,它之前使用的 CPU 会释放出来,这个时候再从就绪队列里,拿一个新的进程过来运行。其实还有很多其他场景,也会触发进程调度,在这里我给你逐个梳理下。

其一,为了保证所有进程可以得到公平调度,CPU 时间被划分为一段段的时间片,这些时间片再被轮流分配给各个进程。这样,当某个进程的时间片耗尽了,就会被系统挂起,切换到其它正在等待 CPU 的进程运行。

其二,进程在系统资源不足(比如内存不足)时,要等到资源满足后才可以运行,这个时候进程也会被挂起,并由系统调度其他进程运行。

其三,当进程通过睡眠函数 sleep 这样的方法将自己主动挂起时,自然也会重新调度。

其四,当有优先级更高的进程运行时,为了保证高优先级进程的运行,当前进程会被挂起,由高优先级进程来运行。

最后一个,发生硬件中断时,CPU 上的进程会被中断挂起,转而执行内核中的中断服务程序。

了解这几个场景是非常有必要的,因为一旦出现上下文切换的性能问题,它们就是幕后凶手。

CPU上下文切换场景2-线程上下文切换(两种情况)

线程与进程最大的区别在于,线程是调度的基本单位,而进程则是资源拥有的基本单位。

所谓内核中的任务调度,实际上的调度对象是线程;而进程只是给线程提供了虚拟内
存、全局变量等资源。所以,对于线程和进程,我们可以这么理解:

当进程只有一个线程时,可以认为进程就等于线程。
当进程拥有多个线程时,这些线程会共享相同的虚拟内存和全局变量等资源。这些资源在上下文切换时是不需要修改的。
另外,线程也有自己的私有数据,比如栈和寄存器等,这些在上下文切换时也是需要保存的。

这么一来,线程的上下文切换其实就可以分为两种情况

第一种, 前后两个线程属于不同进程。此时,因为资源不共享,所以切换过程就跟进程上下文切换是一样。
第二种,前后两个线程属于同一个进程。此时,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据、寄存器等不共享的数据。

到这里你应该也发现了,虽然同为上下文切换,但同进程内的线程切换,要比多进程间的切换消耗更少的资源,而这,也正是多线程代替多进程的一个优势。

CPU上下文切换场景3–中断上下文切换

除了前面两种上下文切换,还有一个场景也会切换 CPU 上下文,那就是中断。

为了快速响应硬件的事件,中断处理会打断进程的正常调度和执行,转而调用中断处理程
序,响应设备事件。而在打断其他进程时,就需要将进程当前的状态保存下来,这样在中断
结束后,进程仍然可以从原来的状态恢复运行。

跟进程上下文不同,中断上下文切换并不涉及到进程的用户态。所以,即便中断过程打断了
一个正处在用户态的进程,也不需要保存和恢复这个进程的虚拟内存、全局变量等用户态资
源。中断上下文,其实只包括内核态中断服务程序执行所必需的状态,包括 CPU 寄存器、
内核堆栈、硬件中断参数等。

对同一个 CPU 来说,中断处理比进程拥有更高的优先级,所以中断上下文切换并不会与进
程上下文切换同时发生。同样道理,由于中断会打断正常进程的调度和执行,所以大部分中
断处理程序都短小精悍,以便尽可能快的执行结束。

另外,跟进程上下文切换一样,中断上下文切换也需要消耗 CPU,切换次数过多也会耗费
大量的 CPU,甚至严重降低系统的整体性能。所以,当你发现中断次数过多时,就需要注
意去排查它是否会给你的系统带来严重的性能问题。

自愿上下文切换与非自愿上下文切换

这两个概念你一定要牢牢记住,因为它们意味着不同的性能问题:
所谓自愿上下文切换,是指进程无法获取所需资源,导致的上下文切换。比如说, I/O、内存等系统资源不足时,就会发生自愿上下文切换。

非自愿上下文切换,则是指进程由于时间片已到等原因,被系统强制调度,进而发生的上下文切换。比如说,大量进程都在争抢 CPU 时,就容易发生非自愿上下文切换。

CPU性能指标03-平均负载

平均负载是指单位时间内,系统处于可运行状态和不可中断状态的平均进程数,也就是平均活跃进程数,它和 CPU 使用率并没有直接关系。

平均负载其实就是平均活跃进程数。平均活跃进程数,直观上的
理解就是单位时间内的活跃进程数,但它实际上是活跃进程数的指数衰减平均值。这个“指
数衰减平均”的详细含义你不用计较,这只是系统的一种更快速的计算方式,你把它直接当
活跃进程数的平均值也没问题。

在评判平均负载时,首先你要知道系统有几个 CPU,这可以通过 top 命令或者从文件 /proc/cpuinfo 中读取,比如:
有了 CPU 个数,我们就可以判断出,当平均负载比 CPU 个数还大的时候,系统已经出现了过载。

不过,且慢,新的问题又来了。我们在例子中可以看到,平均负载有三个数值,到底该参考哪一个呢?

实际上,都要看。三个不同时间间隔的平均值,其实给我们提供了,分析系统负载趋势的数据来源,让我们能更全面、更立体地理解目前的负载状况。

  1. 如果 1 分钟、5 分钟、15 分钟的三个值基本相同,或者相差不大,那就说明系统负载很平稳。
  2. 但如果 1 分钟的值远小于 15 分钟的值,就说明系统最近 1 分钟的负载在减少,而过去 15 分钟内却有很大的负载。
  3. 反过来,如果 1 分钟的值远大于 15 分钟的值,就说明最近 1 分钟的负载在增加,这种增加有可能只是临时性的,也有可能还会持续增加下去,所以就需要持续观察。一旦 1 分钟的平均负载接近或超过了 CPU 的个数,就意味着系统正在发生过载的问题,这时就得分析调查是哪里导致的问题,并要想办法优化了。

那么,在实际生产环境中,平均负载多高时,需要我们重点关注呢?

在我看来,当平均负载高于 CPU 数量 70% 的时候,你就应该分析排查负载高的问题了。一旦负载过高,就可能导致进程响应变慢,进而影响服务的正常功能。

但 70% 这个数字并不是绝对的,最推荐的方法,还是把系统的平均负载监控起来,然后根据更多的历史数据,判断负载的变化趋势。当发现负载有明显升高趋势时,比如说负载翻倍了,你再去做分析和调查。

平均负载和cpu使用率的关系

平均负载是指单位时间内,处于可运行状态和不可中断状态的进程数。所以,它不仅包括了正在使用 CPU 的进程,还包括等待 CPU 和等待I/O 的进程。

而 CPU 使用率,是单位时间内 CPU 繁忙情况的统计,跟平均负载并不一定完全对应。比如:

CPU 密集型进程,使用大量 CPU 会导致平均负载升高,此时这两者是一致的;

I/O 密集型进程,等待 I/O 也会导致平均负载升高,但 CPU 使用率不一定很高;

大量等待 CPU 的进程调度也会导致平均负载升高,此时的 CPU 使用率也会比较高。

CPU性能指标04-CPU缓存命中率

由于 CPU 缓存由更快的SRAM 构成(内存是由 DRAM 构成的),而且离 CPU 核心更近,如果运算时需要的输入
数据是从 CPU 缓存,而不是内存中读取时,运算速度就会快很多。

CPU 的多级缓存架构
CPU 缓存通常分为大小不等的三级缓存。
由于 CPU 缓存由更快的
SRAM 构成(内存是由 DRAM 构成的),而且离 CPU 核心更近,如果运算时需要的输入
数据是从 CPU 缓存,而不是内存中读取时,运算速度就会快很多。

CPU 缓存的材质 SRAM 比内存使用的 DRAM 贵许多,所以不同于内存动辄以 GB 计算,
它的大小是以 MB 来计算的。比如,在我的 Linux 系统上,离 CPU 最近的一级缓存是
32KB,二级缓存是 256KB,最大的三级缓存则是 20MB(Windows 系统查看缓存大小可
以用 wmic cpu 指令,或者用CPU-Z这个工具)。

linux系统下查看cpu缓存的方法:
cat /sys/devices/system/cpu/cpu0/cache/index0/size
cat /sys/devices/system/cpu/cpu0/cache/index1/size
cat /sys/devices/system/cpu/cpu0/cache/index2/size
cat /sys/devices/system/cpu/cpu0/cache/index3/size
在这里插入图片描述
在这里插入图片描述
程序执行时,会先将内存中的数据载入到共享的三级缓存中,再进入每颗核心独有的二级缓
存,最后进入最快的一级缓存,之后才会被 CPU 使用,就像下面这张图。
在这里插入图片描述
缓存要比内存快很多。CPU 访问一次内存通常需要 100 个时钟周期以上,而访问一级缓存
只需要 4~5 个时钟周期,二级缓存大约 12 个时钟周期,三级缓存大约 30 个时钟周期
(对于 2GHZ 主频的 CPU 来说,一个时钟周期是 0.5 纳秒。你可以在 LZMA 的
Benchmark中找到几种典型 CPU 缓存的访问速度)。

如果 CPU 所要操作的数据在缓存中,则直接读取,这称为缓存命中。命中缓存会带来很大
的性能提升,因此,我们的代码优化目标是提升 CPU 缓存的命中率。

当然,缓存命中率是很笼统的,具体优化时还得一分为二。比如,你在查看 CPU 缓存时会
发现有 2 个一级缓存(比如 Linux 上就是上图中的 index0 和 index1),这是因为,CPU
会区别对待指令与数据。比如,“1+1=2”这个运算,“+”就是指令,会放在一级指令缓
存中,而“1”这个输入数字,则放在一级数据缓存中。虽然在冯诺依曼计算机体系结构
中,代码指令与数据是放在一起的,但执行时却是分开进入指令缓存数据缓存的,因此我
们要分开来看二者的缓存命中率。

提升数据缓存的命中率

如果你在用 Linux 操作系统,可以通过一个名叫 Perf 的工具直观地验证缓存命中的情况
(可以用 yum install perf 或者 apt-get install perf 安装这个工具,这个网址中有大量
案例可供参考)

执行 perf stat 可以统计出进程运行时的系统信息(通过 -e 选项指定要统计的事件,如果
要查看三级缓存总的命中率,可以指定缓存未命中 cache-misses 事件,以及读取缓存次数
cache-references 事件,两者相除就是缓存的未命中率,用 1 相减就是命中率。类似的,
通过 L1-dcache-load-misses 和 L1-dcache-loads 可以得到 L1 缓存的命中率),此时你
会发现 array[i][j]的缓存命中率远高于 array[j][i]。
在这里插入图片描述
在这里插入图片描述

提升指令缓存的命中率

pass

提升多核 CPU 下的缓存命中率

虽然三级缓存面向所有核心,但一、二级缓存是每颗核心独享的。我们知道,即使只有一个
CPU 核心,现代分时操作系统都支持许多进程同时运行。这是因为操作系统把时间切成了
许多片,微观上各进程按时间片交替地占用 CPU,这造成宏观上看起来各程序同时在执
行。

因此,操作系统提供了将进程或者线程绑定到某一颗 CPU 上运行的能力。如 Linux 上提供
了 sched_setaffinity 方法实现这一功能,其他操作系统也有类似功能的 API 可用。我在
GitHub 上提供了一个示例程序(代码见这里),你可以看到,当多线程同时执行密集计
算,且 CPU 缓存命中率很高时,如果将每个线程分别绑定在不同的 CPU 核心上,性能便
会获得非常可观的提升。Perf 工具也提供了 cpu-migrations 事件,它可以显示进程从不
同的 CPU 核心上迁移的次数。

  • 13
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值