Linux 之 性能优化

uptime

$ uptime -p
up 1 week, 1 day, 21 hours, 27 minutes

$ uptime
 12:04:11 up 8 days, 21:27,  1 user,  load average: 0.54, 0.32, 0.23
  • "12:04:11" 表示当前时间
  • "up 8 days, 21:27," 表示运行了多长时间
  • "load average: 0.54, 0.32, 0.23"
  • "1 user" 表示 正在登录的用户数
  • "load average: 0.54, 0.32, 0.23",是 过去 1 分钟、5 分钟、15 分钟的平均负载(Load Average)。
Load Average

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

  • 可运行状态的进程,是指正在使用 CPU 或者正在等待 CPU 的进程,也就是我们常用 ps 命令看到的,处于 R 状态(Running 或 Runnable)的进程。
  • 不可中断状态的进程则是正处于内核态关键流程中的进程,并且这些流程是不可打断的,比如最常见的是等待硬件设备的 I/O 响应,也就是我们在ps 命令中看到的 D 状态(Uninterruptible Sleep,也称为 Disk Sleep)的进程。

当一个进程向磁盘读写数据时,为了保证数据的一致性,在得到磁盘回复前,它是不能被其他进程或者中断打断的,这个时候的进程就处于不可中断状态。如果此时的进程被打断了,就容易出现磁盘数据与进程数据不一致的问题。

所以,不可中断状态实际上是系统对进程和硬件设备的一种保护机制。

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

$ ps aux | more
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root          1  0.0  0.0  55040  4452 ?        Ss   Dec05   4:01 /usr/lib/systemd/systemd --switched-root --syst
em --deserialize 22
root          2  0.0  0.0      0     0 ?        S    Dec05   0:00 [kthreadd]
root          3  0.0  0.0      0     0 ?        I<   Dec05   0:00 [rcu_gp]
root          4  0.0  0.0      0     0 ?        I<   Dec05   0:00 [rcu_par_gp]
root          6  0.0  0.0      0     0 ?        I<   Dec05   0:00 [kworker/0:0H-kb]
root          8  0.0  0.0      0     0 ?        I<   Dec05   0:00 [mm_percpu_wq]
root          9  0.0  0.0      0     0 ?        S    Dec05   0:19 [ksoftirqd/0]
root         10  0.0  0.0      0     0 ?        I    Dec05   4:50 [rcu_sched]
STAT进程状态

R:runing,表示当前正在运行的进程

S:sleep,当前正在睡眠的进程

T:stopped,当前停止运行的进程

D:当前不可中断的进程

Z:zombie,僵尸进程,即进程已终止,但却无法被移除至内存外

STAT状态后的内容含义

< 表示进程运行在高优先级上

N 表示进程运行在低优先级上

L 表示进程有页面锁定在内存中

s 表示进程是控制进程

l 表示进程是多进程

+表示当前进程运行在前台

那么最理想的,就是每个 CPU 上都刚好运行着一个进程,这样每个 CPU 都得到了充分利用。比如当平均负载为 2 时。

  • 在只有 2 个 CPU 的系统上,意味着所有的 CPU 都刚好被完全占用。
  • 在 4 个 CPU 的系统上,意味着 CPU 有 50% 的空闲。
  • 而在只有 1 个 CPU 的系统中,则意味着有一半的进程竞争不到 CPU。

查看CPU 的个数

grep 'model name' /proc/cpuinfo | wc -l

当平均负载比 CPU 个数还大的时候,系统已经出现了过载。

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

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

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

平均负载与 CPU 使用率

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

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

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

性能分析

sysstat 包含了常用的 Linux 性能工具,用来监控和分析系统的性能

这个包有两个常用命令 mpstat 和 pidstat。

  • mpstat 是一个常用的多核 CPU 性能分析工具,用来实时查看每个 CPU 的性能指标,以及所有 CPU 的平均指标。
  • pidstat 是一个常用的进程性能分析工具,用来实时查看进程的 CPU、内存、I/O 以及上下文切换等性能指标。
stress 实验
模拟一个 CPU 使用率 100% 的场景
yum  install -y stress
$ stress --cpu 1 --timeout 600
stress: info: [316930] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd

终端运行 stress 命令,模拟一个 CPU 使用率 100% 的场景

$ watch -d uptime

Every 2.0s: uptime                                                                       Thu Dec 14 14:19:17 2023

 14:19:17 up 8 days, 23:42,  2 users,  load average: 0.66, 0.23, 0.16

这边可以 1 分钟的平均负载会慢慢增加到 1.00

$ watch -d uptime

Every 2.0s: uptime                                                                       Thu Dec 14 14:24:53 2023

 14:24:53 up 8 days, 23:48,  3 users,  load average: 1.73, 1.14, 0.59

# -P ALL 表示监控所有CPU,后面数字5表示间隔5秒后输出一组数据
$ mpstat -P ALL 5
Linux 5.4.xxx 	2023年12月14日 	_x86_64_	(4 CPU)

14时20分34秒  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
14时20分39秒  all   25.78    0.00    0.40    0.10    0.00    0.05    0.00    0.00    0.00   73.67
14时20分39秒    0    1.21    0.00    0.60    0.00    0.00    0.00    0.00    0.00    0.00   98.19
14时20分39秒    1    0.80    0.00    0.60    0.00    0.00    0.00    0.00    0.00    0.00   98.59
14时20分39秒    2    0.80    0.00    0.40    0.00    0.00    0.00    0.00    0.00    0.00   98.79
14时20分39秒    3  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00

# 间隔5秒后输出一组数据
$ pidstat -u 5 1
14时26分45秒   UID       PID    %usr %system  %guest    %CPU   CPU  Command
14时26分50秒     0    316931   99.80    0.00    0.00   99.80     3  stress
I/O 密集型进程 模拟
$ stress -i 1 --timeout 600
大量进程的场景
$ stress -c 8 --timeout 600

CPU

CPU 的上下文切换就可以分为几个不同的场景,也就是

  • 进程上下文切换、
  • 线程上下文切换
  • 以及中断上下文切换。

根据 Tsuna 的测试报告,每次上下文切换都需要几十纳秒到数微秒的 CPU 时间。

系统调用

进程既可以在用户空间运行,又可以在内核空间中运行。进程在用户空间运行时,被称为进程的用户态,而陷入内核空间的时候,被称为进程的内核态。

系统调用过程通常称为特权模式切换,而不是上下文切换。

进程和线程

线程是调度的基本单位,而进程则是资源拥有的基本单位。

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

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

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

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

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

profiling

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

# 每隔5秒输出1组数据
$ vmstat 5
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      0 7005360  91564 818900    0    0     0     0   25   33  0  0 100  0  0
  • cs(context switch)是每秒上下文切换的次数。

  • in(interrupt)则是每秒中断的次数。

  • r(Running or Runnable)是就绪队列的长度,也就是正在运行和等待 CPU 的进程数。

  • b(Blocked)则是处于不可中断睡眠状态的进程数。

    每隔5秒输出1组数据

    $ pidstat -w 5
    Linux 4.15.0 (ubuntu) 09/23/18 x86_64 (2 CPU)

    08:18:26 UID PID cswch/s nvcswch/s Command
    08:18:31 0 1 0.20 0.00 systemd
    08:18:31 0 8 5.40 0.00 rcu_sched
    ...

这个结果中有两列内容是我们的重点关注对象。一个是 cswch ,表示每秒自愿上下文切换(voluntary context switches)的次数,另一个则是 nvcswch ,表示每秒非自愿上下文切换(non voluntary context switches)的次数。

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

sysbench 是一个多线程的基准测试工具,一般用来评估不同系统参数下的数据库负载情况。

yum install -y sysbench

# 以10个线程运行5分钟的基准测试,模拟多线程切换的问题
$ sysbench --threads=10 --max-time=300 threads run

# 每隔1秒输出1组数据(需要Ctrl+C才结束)
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 6  0      0 6487428 118240 1292772    0    0     0     0 9019 1398830 16 84  0  0  0
 8  0      0 6487428 118240 1292772    0    0     0     0 10191 1392312 16 84  0  0  0

cs 列的上下文切换次数从之前的 35 骤然上升到了 139 万。同时,注意观察其他几个指标:

  • r 列:就绪队列的长度已经到了 8,远远超过了系统 CPU 的个数 2,所以肯定会有大量的 CPU 竞争。

  • us(user)和 sy(system)列:这两列的 CPU 使用率加起来上升到了 100%,其中系统 CPU 使用率,也就是 sy 列高达 84%,说明 CPU 主要是被内核占用了。

  • in 列:中断次数也上升到了 1 万左右,说明中断处理也是个潜在的问题。

    每隔1秒输出1组数据(需要 Ctrl+C 才结束)

    -w参数表示输出进程切换指标,而-u参数则表示输出CPU使用指标

    $ pidstat -w -u 1
    08:06:33 UID PID %usr %system %guest %wait %CPU CPU Command
    08:06:34 0 10488 30.00 100.00 0.00 0.00 100.00 0 sysbench
    08:06:34 0 26326 0.00 1.00 0.00 0.00 1.00 0 kworker/u4:2

    08:06:33 UID PID cswch/s nvcswch/s Command
    08:06:34 0 8 11.00 0.00 rcu_sched
    08:06:34 0 16 1.00 0.00 ksoftirqd/1
    08:06:34 0 471 1.00 0.00 hv_balloon
    08:06:34 0 1230 1.00 0.00 iscsid
    08:06:34 0 4089 1.00 0.00 kworker/1:5
    08:06:34 0 4333 1.00 0.00 kworker/0:3
    08:06:34 0 10499 1.00 224.00 pidstat
    08:06:34 0 26326 236.00 0.00 kworker/u4:2
    08:06:34 1000 26784 223.00 0.00 sshd

查看线程上下文切换

# 每隔1秒输出一组数据(需要 Ctrl+C 才结束)
# -wt 参数表示输出线程的上下文切换指标
$ pidstat -wt 1
08:14:05      UID      TGID       TID   cswch/s nvcswch/s  Command
...
08:14:05        0     10551         -      6.00      0.00  sysbench
08:14:05        0         -     10551      6.00      0.00  |__sysbench
08:14:05        0         -     10552  18911.00 103740.00  |__sysbench
08:14:05        0         -     10553  18915.00 100955.00  |__sysbench
08:14:05        0         -     10554  18827.00 103954.00  |__sysbench
...

pidstat 只是一个进程的性能分析工具,并不提供任何关于中断的详细信息,怎样才能知道中断发生的类型呢?

从 /proc/interrupts 这个只读文件中读取。/proc 实际上是 Linux 的一个虚拟文件系统,用于内核空间与用户空间之间的通信。/proc/interrupts 就是这种通信机制的一部分,提供了一个只读的中断使用情况。

运行下面的命令,观察中断的变化情况:

# -d 参数表示高亮显示变化的区域
$ watch -d cat /proc/interrupts
           CPU0       CPU1
...
RES:    2450431    5279697   Rescheduling interrupts
...

变化速度最快的是重调度中断(RES),这个中断类型表示,唤醒空闲状态的 CPU 来调度新的任务运行。这是多处理器系统(SMP)中,调度器用来分散任务到不同 CPU 的机制,通常也被称为处理器间中断(Inter-Processor Interrupts,IPI)。

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

####. CPU 使用率

$ grep 'CONFIG_HZ=' /boot/config-$(uname -r)
CONFIG_HZ=1000

即每秒钟触发 1000 次时间中断。

节拍率 HZ 是内核选项,所以用户空间程序并不能直接访问。为了方便用户空间程序,内核还提供了一个用户空间节拍率 USER_HZ,它总是固定为 100,也就是 1/100 秒。这样,用户空间程序并不需要关心内核中 HZ 被设置成了多少,因为它看到的总是固定值 USER_HZ。

# 只保留各个CPU的数据
$ cat /proc/stat | grep ^cpu
cpu  280580 7407 286084 172900810 83602 0 583 0 0 0
cpu0 144745 4181 176701 86423902 52076 0 301 0 0 0
cpu1 135834 3226 109383 86476907 31525 0 282 0 0 0

第一列表示的是 CPU 编号,如 cpu0、cpu1 ,而第一行没有编号的 cpu ,表示的是所有 CPU 的累加。其他列则表示不同场景下 CPU 的累加节拍数,它的单位是 USER_HZ,也就是 10 ms(1/100 秒),所以这其实就是不同场景下的 CPU 时间。

相关推荐
运维老司机7 分钟前
Jenkins修改LOGO
运维·自动化·jenkins
D-海漠23 分钟前
基础自动化系统的特点
运维·自动化
我言秋日胜春朝★32 分钟前
【Linux】进程地址空间
linux·运维·服务器
繁依Fanyi1 小时前
简易安卓句分器实现
java·服务器·开发语言·算法·eclipse
C-cat.1 小时前
Linux|环境变量
linux·运维·服务器
yunfanleo1 小时前
docker run m3e 配置网络,自动重启,GPU等 配置渠道要点
linux·运维·docker
m51271 小时前
LinuxC语言
java·服务器·前端
运维-大白同学1 小时前
将django+vue项目发布部署到服务器
服务器·vue.js·django
糖豆豆今天也要努力鸭2 小时前
torch.__version__的torch版本和conda list的torch版本不一致
linux·pytorch·python·深度学习·conda·torch
烦躁的大鼻嘎2 小时前
【Linux】深入理解GCC/G++编译流程及库文件管理
linux·运维·服务器