Linux操作系统学习笔记(二十五)CPU性能优化

一. 前言

  本文介绍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
2
3
# wc表示word count, -l表示显示文本的行数, 每行表示一个CPU,因此行数表示有几个CPU
ty@ubuntu:~$ grep 'model name' /proc/cpuinfo | wc -l
1

  平均负载的查询可以通过uptime进行,实时状态变化则使用watch -d uptime

1
2
3
# watch 表示实时变化,-d表示高亮变化的位,uptime 
$ watch -d uptime
02:34:03 up 2 days, 20:14, 1 user, load average: 0.63, 0.83, 0.88

  平均负载是指单位时间内,处于可运行状态和不可中断状态的进程数。所以,它不仅包括了正在使用 CPU 的进程,还包括等待 CPU 和等待 I/O 的进程。而 CPU 使用率是单位时间内 CPU 繁忙情况的统计,跟平均负载并不一定完全对应。

  • CPU 密集型进程和大量等待 CPU 的进程调度,使用大量 CPU 会导致平均负载升高,此时这两者是一致的;
  • I/O 密集型进程,等待 I/O 也会导致平均负载升高,但 CPU 使用率不一定很高;

  为了监测CPU的使用率,通常可以使用mpstatpidstat

  • mpstat 是一个常用的多核 CPU 性能分析工具,用来实时查看每个 CPU 的性能指标,以及所有 CPU 的平均指标。
  • pidstat 是一个常用的进程性能分析工具,用来实时查看进程的 CPU、内存、I/O 以及上下文切换等性能指标,可以查看到底是哪个进程导致了 CPU 使用率上升。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# -P ALL 表示监控所有CPU,数字5表示间隔5秒后输出一组数据,后面再加个单独的1的话表示仅输出1组即退出
ty@ubuntu:~$ mpstat -P ALL 5
Linux 4.15.0-112-generic (ubuntu) 08/28/2020 _x86_64_ (1 CPU)

06:28:36 AM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
06:28:41 AM all 0.00 0.00 0.40 0.00 0.00 0.00 0.00 0.00 0.00 99.60
06:28:41 AM 0 0.00 0.00 0.40 0.00 0.00 0.00 0.00 0.00 0.00 99.60

# 间隔5秒后输出一组数据
ty@ubuntu:~$ pidstat -u 5 1
Linux 4.15.0-112-generic (ubuntu) 08/28/2020 _x86_64_ (1 CPU)

06:30:04 AM UID PID %usr %system %guest %CPU CPU Command
06:30:09 AM 0 381 0.00 0.20 0.00 0.20 0 vmtoolsd

Average: UID PID %usr %system %guest %CPU CPU Command
Average: 0 381 0.00 0.20 0.00 0.20 - vmtoolsd

2.2 CPU使用率

   CPU 使用率,就是除了空闲时间外的其他时间占总 CPU 时间的百分比。我们就可以从 /proc/stat 中的数据,很容易地计算出 CPU 使用率。当然,也可以用每一个场景的 CPU 时间,除以总的 CPU 时间,计算出每个场景的 CPU 使用率。事实上,为了计算 CPU 使用率,性能工具一般都会取间隔一段时间(比如 3 秒)的两次值,作差后,再计算出这段时间内的平均 CPU 使用率,即

img

   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 recordperf reportperf top 虽然实时展示了系统的性能信息,但它的缺点是并不保存数据,也就无法用于离线或者后续的分析。而 perf record 则提供了保存数据的功能,保存后的数据,需要用 perf report 解析展示。

  碰到常规问题无法解释的 CPU 使用率情况时,首先要想到有可能是短时应用导致的问题,比如有可能是下面这两种情况,可以结合pstree以及perf record分析,也可以使用execsnoop

  • 第一,应用里直接调用了其他二进制程序,这些程序通常运行时间比较短,通过 top 等工具也不容易发现。
  • 第二,应用本身在不停地崩溃重启,而启动过程的资源初始化,很可能会占用相当多的 CPU。

2.3 CPU上下文切换

  vmstat 是一个常用的系统性能分析工具,主要用来分析系统的内存使用情况,也常用来分析 CPU 上下文切换和中断的次数。

1
2
3
4
ty@ubuntu:~$ vmstat 5 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 268 266720 99700 917660 0 0 317 231 77 273 1 1 96 2 0
  • 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# w表示输出进程切换指标,t表示输出线程切换指标,u表示输出CPU使用指标
ty@ubuntu:~$ pidstat -w -u -t 1
Linux 4.15.0-112-generic (ubuntu) 08/29/2020 _x86_64_ (1 CPU)

08:51:22 AM UID TGID TID %usr %system %guest %CPU CPU Command
08:51:23 AM 0 1269 - 2.02 0.00 0.00 2.02 0 Xorg
08:51:23 AM 0 - 1269 1.01 0.00 0.00 1.01 0 |__Xorg
08:51:23 AM 0 - 1616 0.00 1.01 0.00 1.01 0 |__InputThread
08:51:23 AM 1000 - 2638 1.01 0.00 0.00 1.01 0 |__ibus-daemon

08:51:22 AM UID TGID TID cswch/s nvcswch/s Command
08:51:23 AM 0 7 - 9.09 0.00 ksoftirqd/0
08:51:23 AM 0 - 7 9.09 0.00 |__ksoftirqd/0
08:51:23 AM 0 8 - 34.34 0.00 rcu_sched
......

  一般来说,如果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
2
3
4
5
6
7
8
9
10
# 每1秒输出一次,共输出5
ty@ubuntu:~$ dstat 1 5
You did not select any stats, using -cdngy by default.
----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai hiq siq| read writ| recv send| in out | int csw
8 15 54 24 0 0|6105k 89k| 0 0 | 0 0 | 352 1896
0 0 100 0 0 0| 56k 0 | 0 0 | 0 0 | 74 198
0 0 99 0 0 1| 0 0 | 0 0 | 0 0 | 215 483
0 0 100 0 0 0| 0 0 | 120B 0 | 0 0 | 113 298
0 0 100 0 0 0| 0 0 | 60B 0 | 0 0 | 260 634

2.5 软中断

  软中断 CPU 使用率(softirq)升高是一种很常见的性能问题。虽然软中断的类型很多,但实际生产中,我们遇到的性能瓶颈大多是网络收发类型的软中断,特别是网络接收的软中断,除此之外还有可能来自于定时、调度、RCU锁等。通过top命令可以观察软中断是否较多。

  对于软中断较多的情况,可以监控/proc/softirqs来观察到底是哪种软中断过多,主要有TIMER(定时中断)、NET_RX(网络接收)、SCHED(内核调度)、RCU(RCU 锁)等这几个软中断。

1
2
3
4
5
6
7
8
9
10
11
12
$ watch -d cat /proc/softirqs
CPU0 CPU1
HI: 0 0
TIMER: 1083906 2368646
NET_TX: 53 9
NET_RX: 1550643 1916776
BLOCK: 0 0
IRQ_POLL: 0 0
TASKLET: 333637 3930
SCHED: 963675 2293171
HRTIMER: 0 0
RCU: 1542111 1590625

  针对网络收发导致的软中断,我们可以通过sar查看并比较出是否出现了小包问题等,最后通过tcmdump进行更深入的抓包分析。

1
2
3
4
5
6
7
# -n DEV 表示显示网络收发的报告,间隔1秒输出一组数据
$ sar -n DEV 1
15:03:46 IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s %ifutil
15:03:47 eth0 12607.00 6304.00 664.86 358.11 0.00 0.00 0.00 0.01
15:03:47 docker0 6302.00 12604.00 270.79 664.66 0.00 0.00 0.00 0.00
15:03:47 lo 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
15:03:47 veth9f6bbcd 6302.00 12604.00 356.95 664.66 0.00 0.00 0.00 0.05

三. 压测工具

3.1 stress

  stress 是一个 Linux 系统压力测试工具,可以用作异常进程模拟平均负载升高的场景。

  stress基于多进程的,会fork多个进程,导致进程上下文切换,导致us开销很高;

1
2
3
4
# --cpu表示模拟CPU,1表示100%,-i表示I/O压力测试,-c表示进程
$ stress --cpu 1 --timeout 600
$ stress -i 1 --timeout 600
$ stress -c 8 --timeout 600

3.2 sysbench

  sysbench 是一个多线程的基准测试工具,一般用来评估不同系统参数下的数据库负载情况,也可以用于模拟上下文切换过多的问题。

  sysbench基于多线程的,会创建多个线程,单一进程基于内核线程切换,导致sy的内核开销很高;

1
2
3
4
5
6
7
8
9
root@ubuntu:/home/ty# sysbench --num-threads=10 --max-time=300 --max-requests=10000000 --test=threads run
sysbench 0.4.12: multi-threaded system evaluation benchmark

Running the test with following options:
Number of threads: 10

Doing thread subsystem performance test
Thread yields per test: 1000 Locks used: 8
Threads started!

3.3 ab

   ab是apache自带的压力测试工具。ab非常实用,它不仅可以对apache服务器进行网站访问压力测试,也可以对或其它类型的服务器进行压力测试。比如nginx、tomcat、IIS等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# c表示并发数, n表示测试次数, 还可以用t来指定测试时间
ty@ubuntu:~$ ab -c 10 -n 100 http://192.168.11.129:10000/
This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 192.168.11.129 (be patient).....done

Server Software: nginx/1.15.4
Server Hostname: 192.168.11.129
Server Port: 10000

Document Path: /
Document Length: 9 bytes

Concurrency Level: 10
Time taken for tests: 1.038 seconds
Complete requests: 100
Failed requests: 0
Total transferred: 17200 bytes
HTML transferred: 900 bytes
Requests per second: 96.31 [#/sec] (mean)
Time per request: 103.826 [ms] (mean)
Time per request: 10.383 [ms] (mean, across all concurrent requests)
Transfer rate: 16.18 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.2 0 1
Processing: 70 101 25.0 94 248
Waiting: 69 101 25.0 93 248
Total: 71 101 25.1 94 248

Percentage of the requests served within a certain time (ms)
50% 94
66% 95
75% 97
80% 98
90% 130
95% 151
98% 200
99% 248
100% 248 (longest request)

3.4 hping3

  hping3可以用来发送HTTP请求,模拟Nginx的客户端操作

1
2
3
# -S参数表示设置TCP协议的SYN(同步序列号),-p表示目的端口为80
# -i u100表示每隔100微秒发送一个网络帧,通过该方法可以模拟SYN攻击
$ hping3 -S -p 80 -i u100 192.168.0.30

四. 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] 极客时间 系统性能调优必知必会

坚持原创,坚持分享,谢谢鼓励和支持