性能调优(1)—— CPU

Posted by 皮皮潘 on 05-05,2022

性能分析

性能问题的本质,就是系统资源已经达到瓶颈,但请求的处理却还不够快,无法支撑更多的请求,系统资源总的来说主要包括三大类:CPU、内存以及IO,而在IO中又细分为磁盘IO和网络IO两大块。

性能分析主要包括六个步骤:

  1. 选择指标评估应用程序和系统的性能;
  2. 为应用程序和系统设置性能目标;
  3. 进行性能基准测试;
  4. 性能分析定位瓶颈;
  5. 优化系统和应用程序;
  6. 性能监控和告警。

CPU性能调优

CPU使用率

系统指标

CPU使用率是最常见的用来度量CPU使用情况的指标,它是单位时间内 CPU 使用情况的统计,以百分比的方式展示,往往通过topps等指令展示,但是使用率又细分为%user、%nice、 %system、%iowait 、%steal 等等,其详细介绍如下:

名称缩写描述
userus代表用户态 CPU 时间。注意,它不包括下面的 nice 时间,但包括了 guest 时间
niceni代表低优先级用户态 CPU 时间,也就是进程的 nice 值被调整为 1-19 之间时的 CPU 时间。这里注意,nice 可取值范围是 -20 到 19,数值越大,优先级反而越低
systemsys代表内核态 CPU 时间
idleid代表空闲时间。注意,它不包括等待 I/O 的时间(iowait)
iowaitwa代表等待 I/O 的 CPU 时间
irqhi代表处理硬中断的 CPU 时间
sofirqsi代表处理软中断的 CPU 时间
stealst代表当系统运行在虚拟机中的时候,被其他虚拟机占用的 CPU 时间
guestguest代表通过虚拟化运行其他操作系统的时间,也就是运行虚拟机的 CPU 时间

监控工具

除了通过性能分析工具,我们还可以通过直接读取/proc/stat文件来获取上述的各类CPU使用情况,不过在/proc/stat中记录的是从开机到现在CPU 的累加节拍数,它的单位是 USER_HZ,也就是 10 ms(1/100 秒),因为为了计算CPU计算率,性能工具一般都会取间隔一段时间(比如3秒)的两次值,作差后再计算出这段时间内的平均CPU使用率:
1751_1.png
与系统指标类似的,Linux也给每个进程提供了运行情况的统计信息,也就是/proc/[pid]/stat,再通过同样的方式就能计算出每个进程的CPU使用率了。

另外在使用工具获取CPU使用率时,要注意性能分析工具给出的都是间隔一段时间的平均 CPU 使用率,所以要注意间隔时间的设置,特别是用多个工具对比分析时,你一定要保证它们用的是相同的间隔时间。比如对比一下 top 和 ps 这两个工具报告的 CPU 使用率,默认的结果很可能不一样,因为 top 默认使用 3 秒时间间隔,而 ps 使用的却是进程的整个生命周期。

分析方法

perf 是 Linux 2.6.31 以后内置的性能分析工具。它以性能事件采样为基础,不仅可以分析系统的各种事件和内核性能,还可以用来分析指定应用程序的性能问题。

第一种常见用法是 perf top,类似于 top,它能够实时显示占用 CPU 时钟最多的函数或者指令从而可以用来查找热点函数,对应的结果一共由四列组成:

  • 第一列 Overhead ,是该符号的性能事件在所有采样中的比例,用百分比来表示。
  • 第二列 Shared ,是该函数或指令所在的动态共享对象(Dynamic Shared Object),如内核、进程名、动态链接库名、内核模块名等。
  • 第三列 Object ,是动态共享对象的类型。比如 [.] 表示用户空间的可执行程序、或者动态链接库,而 [k] 则表示内核空间。
  • 最后一列 Symbol 是符号名,也就是函数名。当函数名未知时,用十六进制的地址来表示。

第二种常见用法,也就是 perf record 和 perf report。 perf top 虽然实时展示了系统的性能信息,但它的缺点是并不保存数据,也就无法用于离线或者后续的分析。而 perf record 则提供了保存数据的功能,而保存后的数据,可以用 perf report 解析展示。

另外对于不同CPU使用率异常,我们也要有不同的着重排查点:

  1. 用户 CPU 和 Nice CPU 高,说明用户态进程占用了较多的 CPU,所以应该着重排查进程的性能问题。
  2. 系统 CPU 高,说明内核态占用了较多的 CPU,所以应该着重排查内核线程或者系统调用的性能问题。
  3. I/O 等待 CPU 高,说明等待 I/O 的时间比较长,所以应该着重排查系统存储是不是出现了 I/O 问题。
  4. 软中断和硬中断高,说明软中断或硬中断的处理程序占用了较多的 CPU,所以应该着重排查内核中的中断服务程序。

除此之外,当碰到无法解释的 CPU 使用率高问题时,先要检查一下是不是短时应用在捣鬼,由于监控工具的采样时长以及快照的实现方式,top、pidstat对于一些短时运行或者频繁失败重启的应用往往无法监控到,此时就要使用execsnoop和pstree去解决上述问题了。

平均负载

系统指标

平均负载是指单位时间内,系统处于可运行状态不可中断状态(IO Blocked状态) 的平均进程数,也就是平均活跃进程数而非CPU使用率。当平均负载高于 CPU 数量 70% 的时候,就应该分析排查负载高的问题了

CPU使用率和平均负载不一定完全对应:1. CPU 密集型进程,使用大量 CPU 会导致平均负载升高,此时这两者是一致的;2. I/O 密集型进程,等待 I/O 也会导致平均负载升高,但 CPU 使用率不一定很高;3. 大量等待 CPU 的进程调度也会导致平均负载升高,此时的 CPU 使用率也会比较高,但是都是无效CPU运算。

监视工具

对于平均负载,我们常常会用uptime、mpstat以及pidstat用来监控和分析性能,上述的xxxstat工具都在sysstat包下面,详细介绍如下:

  1. mpstat 是一个常用的多核 CPU 性能分析工具,用来实时查看每个 CPU 的性能指标,以及所有 CPU 的平均指标。
  2. pidstat 是一个常用的进程性能分析工具,用来实时查看进程的 CPU、内存、I/O 以及上下文切换等性能指标。
  3. uptime 可以用来查看系统的启动时间、在线用户以及平均负载

分析方法

平均负载最理想的情况是等于 CPU 个数,当平均负载高于 CPU 数量 70% 的时候,就应该分析排查负载高的问题了。一旦负载过高,就可能导致进程响应变慢,进而影响服务的正常功能。但 70% 这个数字并不是绝对的,最推荐的方法,还是把系统的平均负载监控起来,然后根据更多的历史数据,判断负载的变化趋势。当发现负载有明显升高趋势时,比如说负载翻倍了,你再去做分析和调查。

上下文切换

正如前文所说,就算CPU的使用率用满了,也不一定代表性能就好了,因为CPU可能并没有在干正事,而是在把大量的时间和计算能力花费在上下文切换上

在每个任务运行前,CPU 都需要知道任务从哪里加载、又从哪里开始运行,也就是说,需要系统事先帮它设置好寄存器(上下文环境)和程序计数器(Program Counter,PC)(程序执行起点),这就是所谓的CPU上下文。CPU 上下文切换,就是先把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器以及其他一些信息)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。

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

根据任务的不同,CPU 的上下文切换就可以分为几个不同的场景,也就是:进程上下文切换、线程上下文切换以及中断上下文切换。

进程上下文切换

进程的上下文既包括了最基本的PC以及寄存器等信息,还包括了虚拟内存、栈、全局变量等用户空间的资源,也包括了内核堆栈、寄存器等内核空间的状态。另外Linux 通过 TLB(Translation Lookaside Buffer)来缓存虚拟内存到物理内存的映射关系。当虚拟内存更新后,TLB 也需要刷新,导致内存的访问也会随之变慢。因此进程上下文切换是给系统性能带来最大损耗的一类上下文切换了。

线程上下文切换

线程相较于进程而言,由于同一进程的线程共享虚拟内存、全局变量等资源,因此在发生上下文切换时所需要保存和恢复的上下文大小相较于进程而言更轻量级一些。

可以认为进程是资源拥有的基本单位,而线程是调度的基本单位,所谓的内核中的任务调度实际上的调度对象是线程。

因此线程的上下文切换其实就可以分为两种情况:

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

中断上下文切换

跟进程与线程上下文不同,中断上下文切换并不涉及到进程的用户态。所以,即便中断过程打断了一个正处在用户态的进程,也不需要保存和恢复这个进程的虚拟内存、全局变量等用户态资源。中断上下文,其实只包括内核态中断服务程序执行所必需的状态,包括 CPU 寄存器、内核堆栈、硬件中断参数等,因此中断上下文切换更为轻量,同时中断又分为硬中断和软中断,刚刚提及的中断上下文切换针对的是硬中断,它会打断当前程序的执行并且往往只执行一些必要的且简短的处理,然后将需要耗时执行的任务交由ksoftirqd内核线程执行对应的软中断。

过去的系统调用也是通过 int 0x80 软中断去实现的,因此本质上也是中断上下文切换,但是由于系统调用所需要保存的上下文没有中断那么多,且int 0x80指令的性能确实不如 call/jmp等指令,因此现在改为了通过syscall软件中断去实现,只是还是比普通函数调用多一些cpu指令周期,因为除了要保存更多的信息之外,系统调用还需要切换CPU的特权等级,Linux 按照特权等级,把进程的运行空间分为内核空间和用户空间,其中内核空间(Ring 0)具有最高权限,可以直接访问所有资源;用户空间(Ring 3)只能访问受限资源,不能直接访问内存等硬件设备,必须通过系统调用陷入到内核中,才能访问这些特权资源。

指标与监控工具

vmstat: 是一个常用的系统性能分析工具,主要用来分析系统的内存使用情况,也常用来分析 CPU 上下文切换和中断的次数,需要特别关注的四列内容有:

名称缩写描述
context switchcs每秒上下文切换的次数
interruptin每秒中断的次数
running or runnablr就绪队列的长度,也就是正在运行和等待 CPU 的进程数
blockedb处于不可中断睡眠状态的进程数

pidstat : vmstat 只给出了系统总体的上下文切换情况,要想查看每个进程的详细情况,就需要使用 pidstat 了,给它加上 -w 选项,就可以查看每个进程上下文切换的情况了,在结果中有两列内容的重点关注对象:

名称含义解释
cswch每秒自愿上下文切换的次数自愿上下文切换是指进程无法获取所需资源,导致的上下文切换。比如说, I/O、内存等系统资源不足时,就会发生自愿上下文切换
nvcswch每秒非自愿上下文切换的次数非自愿上下文切换是指进程由于时间片已到等原因,被系统强制调度,进而发生的上下文切换。比如说,大量进程都在争抢 CPU 时,就容易发生非自愿上下文切换

/proc/interrupts文件:/proc/interrupts只读文件提供了一个只读的中断使用情况的查寻方法,可以通过命令watch -d cat /proc/interrupts实时展示中断使用情况

分析方法

系统的上下文切换次数比较稳定的话,那么从数百到一万以内,都应该算是正常的。但当上下文切换次数超过一万次,或者切换次数出现数量级的增长时,就很可能已经出现了性能问题,此时还需要根据上下文切换的类型再做具体分析:

  1. 自愿上下文切换变多了,说明进程都在等待资源,有可能发生了 I/O 等其他问题;
  2. 非自愿上下文切换变多了,说明进程都在被强制调度,也就是都在争抢 CPU,说明 CPU 的确成了瓶颈;
  3. 中断次数变多了,说明 CPU 被中断处理程序占用,还需要通过查看 /proc/interrupts 文件来分析具体的中断类型。

进程状态

状态含义解释

通过top和ps可以查看进程的状态,其中S列就表示进程的,其中各个状态含义如下:

名称缩写描述
Running/RunnableR进程在 CPU 的就绪队列中,正在运行或者正在等待运行
Disk SleepD不可中断状态睡眠(Uninterruptible Sleep),一般表示进程正在跟硬件交互,并且交互过程不允许被其他进程或中断打断
ZombieZ僵尸进程,也就是进程实际上已经结束了,但是父进程还没有回收它的资源(比如进程的描述符、PID 等)
Interruptible SleepS可中断状态睡眠,表示进程因为等待某个事件而被系统挂起。当进程等待的事件发生时,它会被唤醒并进入 R 状态。
IdleI空闲状态,用在不可中断睡眠的内核线程

分析方法

分析状态时主要关注两类状态:不可中断状态以及僵尸状态:

  1. 不可中断状态,表示进程正在跟硬件交互,为了保护进程数据和硬件的一致性,系统不允许其他进程或中断打断这个进程。进程长时间处于不可中断状态,通常表示系统有 I/O 性能问题。
  2. 僵尸进程表示进程已经退出,但它的父进程还没有回收子进程占用的资源。短暂的僵尸状态我们通常不必理会,但进程长时间处于僵尸状态,就应该注意了,可能有应用程序没有正常处理子进程的退出。

工具汇总

功能工具描述
CPU使用toptop)查看系统以及各个进程的CPU使用率,它集成了uptime、mpstat以及ps等命令,top默认显示每个CPU的使用率,通过输入数字 1 可以切换到每个 CPU 的使用率了
系统负载uptimewatch -d uptime)看三个阶段平均负载
系统整体情况mpstatmpstat -p ALL 3) 查看 每个cpu当前的整体状况,可以重点看用户态、内核态、以及io等待三个参数
系统整体的平均上下文切换情况vmstat(vmstat 3) 可以重点看 r (进行或等待进行的进程)、b (不可中断进程/io进程) 、in (中断次数) 、cs(上下文切换次数)
查看进程的情况pidstatpidstat -w(进程切换指标)/-u(cpu使用指标)/-wt(线程上下文切换指标)) 注意看是自愿上下文切换、还是被动上下文切换
查看硬中断具体情况/proc/interrupts文件通过命令watch -d cat /proc/interrupts实时展示硬中断情况
查看软中断具体情况/proc/softirqs文件通过命令watch -d cat /proc/sofirqs实时展示软中断情况
分析进程的CPU问题perfperf以性能事件采样为基础,不仅可以分析系统的各种事件和内核性能,还可以用来分析指定应用程序的性能问题,常用命令包括perf topperf record
定期运行一个命令来查看输出watchwatch 命令可以定期运行一个命令来查看输出;如果再加上 -d 参数,还可以高亮出变化的部分,从高亮部分我们就可以直观看出,哪些内容变化得更快
同时查看CPU和IO信息dstat通过dstat可以同时查看CPU和IO信息
监控短时进程execsnoopexecsnoop通过ftrace实时监控进程的exec()行为并输出短时进程的基本信息
模拟进程、iostress通过stress可以模拟进程以及IO来对于系统增加负载
模拟线程sysbench通过sysbench可以模拟线程来对于系统增加负载
查看系统的网络收发情况sarsar 可以用来查看系统的网络收发情况,还有一个好处是,不仅可以观察网络收发的吞吐量(BPS,每秒收发的字节数),还可以观察网络收发的 PPS,即每秒收发的网络帧数。

参考

  1. 《Linux性能优化实战》