CPU Diagnosis
平均负载
我们经常会用 uptime、top 等工具查询 cpu 的平均负载(Load Average),那到底什么是平均负载呢?
通过 man uptime 可以看到平均负载的定义:System load averages is the average number of processes that are either in a runnable or uninterruptable state. A process in a runnable state is either using the CPU or waiting to use the CPU. A process in uninterruptable state is wait- ing for some I/O access, eg waiting for disk.
简单来说,即单位时间内系统处于可运行状态或不可中断状态的平均进程数。如平均负载为 2 时,系统有 2 个 CPU,意味着 CPU 刚好被完全占用。三个时间间隔的平均负载反映了趋势。所以平均负载高并不能反映问题出在哪里,可能是 CPU 使用过高,也可能是 IO 等待过长。
可运行状态(ps 命令中看到的 R 状态):
正在使用 CPU
等待使用 CPU
不可中断状态(ps 命令中看到的 D 状态):等待某些硬件设备的 IO 响应。
经验:当平均负载高于 CPU 个数的 70% 的时候,就需要关注性能问题了。
平均负载与 CPU 使用率 两者并不一定是完全对应的,从定义可以看出,平均负载是指活跃的进程个数,活跃的进程还包括了等待 CPU 和等待 IO 的进程。
CPU 密集型:两个是一致的
IO 密集型:等待 IO 会使平均负载升高,但 CPU 使用率不一定高
大量的进程调用会使平均负载升高,CPU 使用率也会升高
压力测试
上下文切换
每个任务运行前,CPU 都需要知道从哪里加载、从哪个位置开始运行,所以系统需要先设置好 CPU 寄存器和程序计数器(Program Counter, PC)。
寄存器是 CPU 内置的容量小、速度极快的内存;PC 用于存储正在执行的指令位置、或下一条指令位置。这两者是 CPU 在运行任何任务前必须依赖的环境,所以也叫 CPU 上下文。
CPU 上下文切换就是把前一个任务的上下文(寄存器和 PC)保存到内核中,加载新任务的上下文到寄存器和 PC。
根据任务和不同,可以分为进程上下文切换、线程上下文切换、中断上下文切换。
系统调用
Linux 把进程的运行空间分为内核空间(最高权限,可以访问所有资源)和用户空间(必须通过系统调用陷入到内核中才能访问内存等硬件设备),分别对应 Ring0 和 Ring3。
系统调用时,CPU 寄存器中用户态的指令位置需要先保存起来,更新为内核态指令的位置;系统调用结束后,又恢复为用户态。所以一次系统调用会发生两次 CPU 上下文切换。
系统调用不需要涉及虚拟内存等资源,也不会切换进程,所以系统调用通常称为特权模式切换,而不是上下文切换。
进程上下文切换
进程是由内核来管理和调度的,进程切换只能发生在内核态。所以进程切换不仅包括用户态资源(虚拟内存、栈、全局变量),还包括内核态资源(内核堆栈、寄存器)。
Linux 为每个 CPU 维护一个队列,将活跃进程(正在运行或正在等待 CPU)按照优先级和等待 CPU 的时长排序。进程调度的时机发生在:
一个进程执行结束。
分时操作系统中进程的时间片用完了
进程通过 sleep 将自己主动挂起
有更高优先级的进程
硬件中断
线程上下文切换
线程是调度的基本单位,而进程是资源拥有的基本单位。内核中的任务调度对象都是线程,而进程只是给线程提供了虚拟内存、全局变量等资源。所以:
进程只有一个线程时,进程就等于线程。
进程有多个线程时,线程会共享相同的虚拟内存和全局变量等资源,这些资源在上下文切换时不需要修改。
线程也有自己的私有数据,如栈和寄存器,这些在上下文切换是需要保存的。
由此可见,若两个线程不属于同一进程,则上下文切换与进程切换一样;若属于同一进程,切换时只需要切换线程的私有数据。这也是多线程替代多进程的一个优势。
中断上下文切换
为了快速响应硬件事件,中断会打断进程的正常执行,转而调度中断处理程序。中断不涉及进程的用户态,所以中断上下文切换不需要保存和恢复用户态资源。对于同一 CPU, 中断处理比进程有更高的优先级。
案例
总结
上下文切换 1 万以内都算正常,若超过 1 万次、或出现量级增长,就可能出现了性能问题。不过这是经验数据,具体还要看配置等实际场景。
自愿上下文切换变多,说明进程都在等待资源,可能发生了 IO 等其它问题。
非自愿上下文切换变多,说明都在争抢 CPU,确实 CPU 出现了瓶颈。
中断次数变多,需要查看 /proc/interrupts 具体分析。
使用率
Linux 将 CPU 运行划分成很短的时间片,通过定义节拍率(HZ)来触发时间中断,Jiffies 记录了开机以来的节拍数。
用户空间的节拍率 USER_HZ 固定为 100。
Linux 通过 /proc 虚拟文件系统,向用户提供了内核信息。/proc/stat 提供了 CPU 和任务统计信息,进程的的 CPU 使用情况可以查看 /proc/[pid]/stat。
可通过 man proc 查看每一列的意义:
user:用户态。不包括 nice,包括 guest。
nice:低优先级用户态。
system:内核态。
idle:空闲。不包括 iowait。
iowait:等待 I/O。
irq:硬中断。
softirq:软中断。
steal:若当前系统运行在虚拟机中,CPU 被其它虚拟机占用的时间。
guest:虚拟化运行其它操作系统时,运行虚拟机的时间。
guest_nice:低优先级运行虚拟机的时间。
CPU 使用率为:
上面是开机以来的平均使用率,应该计算某段时间范围内的使用率,所以需要做个差值。需要注意:不同的性能分析工具做差值的时间范围不同,如 top 默认是 3s,ps 是进程的整个生命周期。
通常查看 CPU 使用率的工具有:ps、top、pidstat。可用 perf、GDB 来定位具体的函数。
系统的 CPU 使用率很高,不一定能找到对应高 CPU 使用率的进程。
进程在不断的崩溃重启,如配置错误等等。
应用调用其它二进制程序,这些程序运行时间较短。
若要检测上述问题,可通过:
perf record -g,perf report
execsnoop,专为短时进程设计的工具
不可中断状态
不可中断状态是一种保护机制,可以保证硬件的交互过程不被意外打断。所以,短时间的不可中断状态是很正常的,但一个进程长时间处于 D 状态,通常表示系统有 I/O 性能问题。若系统中出现大量 D 状态的进程,则要考虑下是不是出现了 I/O 性能问题。
但是 iowait 高不一定就是 I/O 有性能瓶颈,当系统中只有 I/O 密集型的进程运行时,iowait 也会很高,但是磁盘还可能远没到读写瓶颈。
可以通过 pidstat、dstat 等工具确认是否有磁盘 I/O 问题。
僵尸进程
大量僵尸进程会占用进程号,导致新进程不能创建。
若系统中出现大量 Z 状态的进程,则要考虑父进程是否回收自己进程的资源。回收的方式:
父进程通过 wait(), waitpid() 等待子进程结束,并回收资源。
父进程可以注册 SIGCHLD 信号处理函数异步回收。
父进程退出,init 进程也会回收。
软中断
softirq 也是 CPU 使用率升高的常见原因。
中断是系统用来响应硬件设备请求的一种机制,会打断进程的正常调度和执行,然后调用内核中的中断处理程序来响应设备的请求。中断是一种异步的事件处理机制,可以提高系统的并发处理能力。
中断处理程序在响应中断时,会临时关闭中断,所以本次中断处理完成之前,其它中断都不能响应,即中断可能会丢失。所以中断处理程序的特点是尽可能快地运行。为了防止中断处理程序执行过长,Linux 将中断分成两个阶段:
上半部:快速处理中断,在中断禁止模式下运行。直接处理硬件请求,常说的硬中断,特点是快速。会打断 CPU 正在执行的任务,立即执行中断处理程序。
下半部:延迟处理上半部未完成的工作,通常以内核线程的方式运行。由内核触发,常说的软中断,特点是延迟执行。每个 CPU 都对应一个软中断内核线程,名字为 ksoftirq/CPU编号。
比如网卡接收到数据,通过硬件中断,内核启动响应的中断处理程序。上半部,网卡把数据读到内存,更新寄存器状态(表示数据已经就绪),再发送一个软中断信号,通知下半部程序做进一步处理;下半部:从内存中读取数据,按照网络协议栈对数据逐层梳理。
软中断不仅包含硬件中断处理程序的下半部,一些内核自定义事件也属于软中断,比如内核调度和 RCU 锁。
总结
CPU 性能指标
指标 -> 工具
工具 -> 指标
指标的关联性
CPU 性能优化
方法论
怎么判断性能优化是否有效?优化后能提升多少性能?
多个性能问题同时发生,应该先优化哪个?80% 的问题都由 20% 的代码导致,并不是所有的问题都值得优化。
有多种优化方法时,选用哪个?
评估优化效果
确定量化指标。不要局限于单一维度,至少从应用程序(如吞吐量、延迟)和系统资源(如 CPU 使用率)两个维度。
测试优化前指标。
测试优化后指标。
应用程序优化
编译器优化
算法优化
异步
多线程替代多进程
缓存
系统优化
CPU 绑定
CPU 独占
优先级调整
为进程设置资源限制
NUMA
中断负载均衡
避免过早优化
Last updated