一. 前言
本文介绍Linux服务器CPU性能评估和优化的基本方法。
二. CPU性能查询工具详解
2.1 平均负载
平均负载是指单位时间内,系统处于可运行状态和不可中断状态的平均进程数,也就是平均活跃进程数,它和 CPU 使用率并没有直接关系。所谓可运行状态的进程,是指正在使用 CPU 或者正在等待 CPU 的进程,也就是我们常用 ps 命令看到的,处于 R 状态(Running 或 Runnable)的进程。不可中断状态的进程则是正处于内核态关键流程中的进程,并且这些流程是不可打断的,比如最常见的是等待硬件设备的 I/O 响应,也就是我们在 ps 命令中看到的 D 状态(Uninterruptible Sleep,也称为 Disk Sleep)的进程。
平均负载最理想的情况是等于 CPU 个数。所以在评判平均负载时,首先要知道系统有几个 CPU,这可以通过 top
命令或者从文件 /proc/cpuinfo
中读取。
1 | wc表示word count, -l表示显示文本的行数, 每行表示一个CPU,因此行数表示有几个CPU |
平均负载的查询可以通过uptime
进行,实时状态变化则使用watch -d uptime
1 | watch 表示实时变化,-d表示高亮变化的位,uptime |
平均负载是指单位时间内,处于可运行状态和不可中断状态的进程数。所以,它不仅包括了正在使用 CPU 的进程,还包括等待 CPU 和等待 I/O 的进程。而 CPU 使用率是单位时间内 CPU 繁忙情况的统计,跟平均负载并不一定完全对应。
- CPU 密集型进程和大量等待 CPU 的进程调度,使用大量 CPU 会导致平均负载升高,此时这两者是一致的;
- I/O 密集型进程,等待 I/O 也会导致平均负载升高,但 CPU 使用率不一定很高;
为了监测CPU的使用率,通常可以使用mpstat
和pidstat
。
mpstat
是一个常用的多核 CPU 性能分析工具,用来实时查看每个 CPU 的性能指标,以及所有 CPU 的平均指标。pidstat
是一个常用的进程性能分析工具,用来实时查看进程的 CPU、内存、I/O 以及上下文切换等性能指标,可以查看到底是哪个进程导致了 CPU 使用率上升。
1 | -P ALL 表示监控所有CPU,数字5表示间隔5秒后输出一组数据,后面再加个单独的1的话表示仅输出1组即退出 |
2.2 CPU使用率
CPU 使用率,就是除了空闲时间外的其他时间占总 CPU 时间的百分比。我们就可以从 /proc/stat 中的数据,很容易地计算出 CPU 使用率。当然,也可以用每一个场景的 CPU 时间,除以总的 CPU 时间,计算出每个场景的 CPU 使用率。事实上,为了计算 CPU 使用率,性能工具一般都会取间隔一段时间(比如 3 秒)的两次值,作差后,再计算出这段时间内的平均 CPU 使用率,即
top 和 ps 是最常用的性能分析工具:
- top 显示了系统总体的 CPU 和内存使用情况,以及各个进程的资源使用情况。
- ps 则只显示了每个进程的资源使用情况。
对比一下 top 和 ps 这两个工具报告的 CPU 使用率,默认的结果很可能不一样,因为 top 默认使用 3 秒时间间隔,而 ps 使用的却是进程的整个生命周期。以下是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使用率高的问题,主要有两种方式可以查找对应的热点函数或进程
- 采用
perf top
,它能够实时显示占用 CPU 时钟最多的函数或者指令,因此可以用来查找热点函数。 - 使用
perf record
和perf report
。perf top
虽然实时展示了系统的性能信息,但它的缺点是并不保存数据,也就无法用于离线或者后续的分析。而perf record
则提供了保存数据的功能,保存后的数据,需要用perf report
解析展示。
碰到常规问题无法解释的 CPU 使用率情况时,首先要想到有可能是短时应用导致的问题,比如有可能是下面这两种情况,可以结合pstree
以及perf record
分析,也可以使用execsnoop
。
- 第一,应用里直接调用了其他二进制程序,这些程序通常运行时间比较短,通过 top 等工具也不容易发现。
- 第二,应用本身在不停地崩溃重启,而启动过程的资源初始化,很可能会占用相当多的 CPU。
2.3 CPU上下文切换
vmstat 是一个常用的系统性能分析工具,主要用来分析系统的内存使用情况,也常用来分析 CPU 上下文切换和中断的次数。
1 | ty@ubuntu:~$ vmstat 5 1 |
- r 和 b 分别表示运行队列和等待IO的进程数量,r显著增加说明当前CPU负荷的运行任务过多
- memory 为内存相关的统计数据
- swpd 表示使用的虚拟内存大小
- free 表示可用内存大小
- buff 表示缓冲区大小
- cache 表示缓存大小
- swap 表示虚拟内存和物理内存页的交换,si为写入内存,so为写出内存至交换区
- io 表示磁盘读写,bi为读取块数,bo为写出块数
- syhstem中in表示中断,cs表示上下文切换数
- cpu 中
- us: 用户进程执行时间(user time)
- sy: 系统进程执行时间(system time)
- id: 空闲时间(包括IO等待时间),中央处理器的空闲时间 。以百分比表示。
- wa: 等待IO时间
除此之外,还可以通过pidstat -w
来细分主动调度和被动调度,即自愿上下文切换cswch(voluntary context switches)和非自愿上下文切换nvcswch(non voluntary context switches)。
- 自愿上下文切换,是指进程无法获取所需资源,导致的上下文切换。比如说, I/O、内存等系统资源不足时,就会发生自愿上下文切换。
- 非自愿上下文切换,则是指进程由于时间片已到等原因,被系统强制调度,进而发生的上下文切换。比如说,大量进程都在争抢 CPU 时,就容易发生非自愿上下文切换。
1 | w表示输出进程切换指标,t表示输出线程切换指标,u表示输出CPU使用指标 |
一般来说,如果CPU上下文切换次数异常升高,主要可以从以下角度考虑
- 自愿上下文切换变多了,说明进程都在等待资源,有可能发生了 I/O 等其他问题;
- 非自愿上下文切换变多了,说明进程都在被强制调度,也就是都在争抢 CPU,说明 CPU 的确成了瓶颈;
- 中断次数变多了,说明 CPU 被中断处理程序占用,还需要通过查看 /proc/interrupts 文件来分析具体的中断类型。
2.4 不可中断进程/僵尸进程
对于此类问题我们通常可以使用top
或者ps
进行观察。首先看看进程状态的分类:
- R 是 Running 或 Runnable 的缩写,表示进程在 CPU 的就绪队列中,正在运行或者正在等待运行。
- D 是 Disk Sleep 的缩写,也就是不可中断状态睡眠(Uninterruptible Sleep),一般表示进程正在跟硬件交互,并且交互过程不允许被其他进程或中断打断。
- Z 是 Zombie 的缩写,它表示僵尸进程,也就是进程实际上已经结束了,但是父进程还没有回收它的资源(比如进程的描述符、PID 等)。
- S 是 Interruptible Sleep 的缩写,也就是可中断状态睡眠,表示进程因为等待某个事件而被系统挂起。当进程等待的事件发生时,它会被唤醒并进入 R 状态。
- I 是 Idle 的缩写,也就是空闲状态,用在不可中断睡眠的内核线程上。前面说了,硬件交互导致的不可中断进程用 D 表示,但对某些内核线程来说,它们有可能实际上并没有任何负载,用 Idle 正是为了区分这种情况。要注意,D 状态的进程会导致平均负载升高, I 状态的进程却不会。
- T 或者 t,也就是 Stopped 或 Traced 的缩写,表示进程处于暂停或者跟踪状态。
- X,也就是 Dead 的缩写,表示进程已经消亡,所以你不会在 top 或者 ps 命令中看到它。
除此之外,还可以使用dstat
命令观察CPU和I/O的使用情况。
1 | # 每1秒输出一次,共输出5次 |
2.5 软中断
软中断 CPU 使用率(softirq)升高是一种很常见的性能问题。虽然软中断的类型很多,但实际生产中,我们遇到的性能瓶颈大多是网络收发类型的软中断,特别是网络接收的软中断,除此之外还有可能来自于定时、调度、RCU锁等。通过top
命令可以观察软中断是否较多。
对于软中断较多的情况,可以监控/proc/softirqs
来观察到底是哪种软中断过多,主要有TIMER(定时中断)、NET_RX(网络接收)、SCHED(内核调度)、RCU(RCU 锁)等这几个软中断。
1 | watch -d cat /proc/softirqs |
针对网络收发导致的软中断,我们可以通过sar
查看并比较出是否出现了小包问题等,最后通过tcmdump
进行更深入的抓包分析。
1 | # -n DEV 表示显示网络收发的报告,间隔1秒输出一组数据 |
三. 压测工具
3.1 stress
stress 是一个 Linux 系统压力测试工具,可以用作异常进程模拟平均负载升高的场景。
stress基于多进程的,会fork多个进程,导致进程上下文切换,导致us开销很高;
1 | --cpu表示模拟CPU,1表示100%,-i表示I/O压力测试,-c表示进程 |
3.2 sysbench
sysbench 是一个多线程的基准测试工具,一般用来评估不同系统参数下的数据库负载情况,也可以用于模拟上下文切换过多的问题。
sysbench基于多线程的,会创建多个线程,单一进程基于内核线程切换,导致sy的内核开销很高;
1 | root@ubuntu:/home/ty# sysbench --num-threads=10 --max-time=300 --max-requests=10000000 --test=threads run |
3.3 ab
ab是apache自带的压力测试工具。ab非常实用,它不仅可以对apache服务器进行网站访问压力测试,也可以对或其它类型的服务器进行压力测试。比如nginx、tomcat、IIS等。
1 | c表示并发数, n表示测试次数, 还可以用t来指定测试时间 |
3.4 hping3
hping3
可以用来发送HTTP请求,模拟Nginx的客户端操作
1 | -S参数表示设置TCP协议的SYN(同步序列号),-p表示目的端口为80 |
四. CPU性能优化思路
4.1 应用程序优化
首先,从应用程序的角度来说,降低 CPU 使用率的最好方法当然是排除所有不必要的工作,只保留最核心的逻辑。比如减少循环的层次、减少递归、减少动态内存分配等等。除此之外,应用程序的性能优化也包括很多种方法。
- 编译器优化:很多编译器都会提供优化选项,适当开启它们,在编译阶段你就可以获得编译器的帮助,来提升性能。比如, gcc 就提供了优化选项 -O2,开启后会自动对应用程序的代码进行优化。
- 算法优化:使用复杂度更低的算法,可以显著加快处理速度。
- 异步处理:使用异步处理,可以避免程序因为等待某个资源而一直阻塞,从而提升程序的并发处理能力。比如,把轮询替换为事件通知,就可以避免轮询耗费 CPU 的问题。
- 多线程代替多进程:前面讲过,相对于进程的上下文切换,线程的上下文切换并不切换进程地址空间,因此可以降低上下文切换的成本。
- 善用缓存:经常访问的数据或者计算过程中的步骤,可以放到内存中缓存起来,这样在下次用时就能直接从内存中获取,加快程序的处理速度。
4.2 系统优化
从系统的角度来说,优化 CPU 的运行,一方面要充分利用 CPU 缓存的本地性,加速缓存访问;另一方面,就是要控制进程的 CPU 使用情况,减少进程间的相互影响。具体来说,系统层面的 CPU 优化方法也有不少。
- CPU 绑定:把进程绑定到一个或者多个 CPU 上,可以提高 CPU 缓存的命中率,减少跨 CPU 调度带来的上下文切换问题。
- CPU 独占:跟 CPU 绑定类似,进一步将 CPU 分组,并通过 CPU 亲和性机制为其分配进程。这样,这些 CPU 就由指定的进程独占,换句话说,不允许其他进程再来使用这些 CPU。
- 优先级调整:使用 nice 调整进程的优先级,正值调低优先级,负值调高优先级。优先级的数值含义前面我们提到过,忘了的话及时复习一下。在这里,适当降低非核心应用的优先级,增高核心应用的优先级,可以确保核心应用得到优先处理。
- 为进程设置资源限制:使用 Linux cgroups 来设置进程的 CPU 使用上限,可以防止由于某个应用自身的问题,而耗尽系统资源。
- NUMA(Non-Uniform Memory Access)优化:支持 NUMA 的处理器会被划分为多个 node,每个 node 都有自己的本地内存空间。NUMA 优化,其实就是让 CPU 尽可能只访问本地内存。
- 中断负载均衡:无论是软中断还是硬中断,它们的中断处理程序都可能会耗费大量的 CPU。开启 irqbalance 服务或者配置 smp_affinity,就可以把中断处理过程自动负载均衡到多个 CPU 上。
总结
优化永远是谋定而后动的事,在做好充分的测评后再有针对性的进行,才能够以最小的代价获得最大的收益。
参考文献
[1] Linux-insides
[2] 深入理解Linux内核
[3] Linux内核设计的艺术
[4] 深入理解计算机系统
[5] 深入理解Linux网络技术内幕
[6] shell脚本编程大全
[7] 极客时间 Linux性能优化实战
[8] 极客时间 系统性能调优必知必会