用户您好!请先登录!

Linux CPU优化实践

Linux CPU优化实践

Linux性能分析概要

1. 性能指标

随着应用负载的增加,系统资源的使用也会升高,甚至达到极限。而性能问题的本质,就是系统资源已经达到瓶颈,但请求的处理却还不够快,无法支撑更多的请求。
性能分析,其实就是找出应用或系统的瓶颈,并设法去避免或者缓解它们,从而更高效地利用系统资源处理更多的请求。这包含了一系列步骤,比如:

  • 选择指标评估应用程序和系统的性能
  • 为应用程序和系统设置性能目标
  • 进行性能基准测试
  • 性能分析定位瓶颈
  • 优化系统和应用程序
  • 性能监控和告警

2. linux性能工具图谱

3. linux性能优化思维导图

平均负载

1. 什么是平均负载?

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

可运行状态的进程: 正在使用 CPU 或者正在等待 CPU 的进程,也就是我们常用 ps 命令看到的处于 R 状态(Running 或 Runnable) 的进程。
不可中断状态的进程: 正处于内核态关键流程中的进程,并且这些流程是不可打断的,比如最常见的是等待硬件设备的 I/O 响应, 也就是我们在 ps 命令中看到的 D 状态(Uninterruptible Sleep, 也称为 Disk Sleep) 的进程。
\color{red}{不可中断状态实际上是系统对进程和硬件设备的一种保护机制。}

2. 平均负载为多少时合理

  • 平均负载最理想的情况是等于 CPU 个数。
    查看系统有几个CPU
$ grep 'model name' /proc/cpuinfo | wc -l
2
  • uptime 给了我们三个不同时间间隔的平均值,给我们提供了分析系统负载趋势的数据来源,让我们更全面、更立体地理解目前的负载情况。
    • 1 分钟5 分钟15 分钟 的三个值基本相同,或者相差不大,说明系统负载很平稳。
    • 如果1 分钟的值远小于15 分钟 的值,说明系统最近 1分钟的负载在减少,而过去15 分钟内却有很大的负载
    • 如果1 分钟 的值远大于 15 分钟的值,就说明最近 1 分钟的负载在增加。一旦 1 分钟的平均负载接近或超过了 CPU 的个数,就意味着系统正在发生过载的问题。

3. 平均负载与CPU使用率

平均负载不仅包括了正在使用 CPU 的进程,还包括了等待CPU等待 I/O的进程。
CPU使用率是指单位时间内 CPU 繁忙情况的统计,跟平均负载并不一定完全对应。比如:

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

4. 检查平均负载的工具

  • uptime: 当前时间、系统运行时间、正在登录用户数、过去1分钟、5分钟、15分钟的平均负载
$ uptime
 00:19:43 up 2 min,  1 user,  load average: 3.15, 1.54, 0.60
# -d 参数表示高亮显示变化的区域
$ watch -d uptime
Every 2.0s: uptime                                      Wed Dec 26 00:21:14 2018
 00:21:14 up 3 min,  1 user,  load average: 0.87, 1.23, 0.58
  • mpstat: 查看 CPU 使用率的变化情况
# -P ALL 表示监控所有 CPU, 后面数字 5 表示间隔 5 秒输出一组数据
$ mpstat -P ALL 5
Linux 4.15.0-42-generic (c5220056-VirtualBox)   12/26/2018  _x86_64_    (2 CPU)

12:22:14 AM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
12:22:19 AM  all    4.55    0.00    3.31    0.00    0.00    0.21    0.00    0.00    0.00   91.93
12:22:19 AM    0    4.32    0.00    3.50    0.00    0.00    0.00    0.00    0.00    0.00   92.18
12:22:19 AM    1    4.80    0.00    2.92    0.00    0.00    0.42    0.00    0.00    0.00   91.86
  • pidsta
    查看进程 CPU 使用率情况
# 间隔 5 秒后输出一组数据
$ pidstat -u 5 1
Linux 4.15.0-42-generic (c5220056-VirtualBox)   12/26/2018  _x86_64_    (2 CPU)

12:24:54 AM   UID       PID    %usr %system  %guest    %CPU   CPU  Command
12:24:59 AM     0      1392    0.00    0.20    0.00    0.20     1  kworker/u4:25
12:24:59 AM     0      5863    0.00    0.20    0.00    0.20     0  dockerd
12:24:59 AM     0      6097    0.00    0.20    0.00    0.20     1  docker-containe
12:24:59 AM     0      7341    1.20    2.20    0.00    3.39     1  Xorg
12:24:59 AM  1000      8425    7.78    0.60    0.00    8.38     1  compiz
12:24:59 AM  1000      8500    0.20    0.00    0.00    0.20     1  vmtoolsd
12:24:59 AM  1000      8808    0.80    0.20    0.00    1.00     0  gnome-terminal-
12:24:59 AM  1000      9563    0.00    0.40    0.00    0.40     0  pidstat

Average:      UID       PID    %usr %system  %guest    %CPU   CPU  Command
Average:        0      1392    0.00    0.20    0.00    0.20     -  kworker/u4:25
Average:        0      5863    0.00    0.20    0.00    0.20     -  dockerd
Average:        0      6097    0.00    0.20    0.00    0.20     -  docker-containe
Average:        0      7341    1.20    2.20    0.00    3.39     -  Xorg
Average:     1000      8425    7.78    0.60    0.00    8.38     -  compiz
Average:     1000      8500    0.20    0.00    0.00    0.20     -  vmtoolsd
Average:     1000      8808    0.80    0.20    0.00    1.00     -  gnome-terminal-
Average:     1000      9563    0.00    0.40    0.00    0.40     -  pidstat

CPU 的上下文切换

在每个任务运行前, CPU 都需要知道任务从哪里加载、又从哪里开始运行、也就是说,需要系统事先给他设置好 CPU 寄存器和程序计数器(Program Counter, PC)
CPU 寄存器:是 CPU 内置的容量小、但速度极快的内存。
程序计数器:是用来存储 CPU 正在执行的指令位置、或者即将执行的下一条指令位置。
它们都是 CPU 在运行任何任务前,比如的依赖环境,因此也被叫做 CPU 上下文

上下文切换:就是先把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。

\color{red}{根据任务的不同}, CPU 的上下文切换可以分为进程上下文切换线程上下文切换以及中断上下文切换

1. 进程上下文切换

Linux 按照特权等级,把进程的运行空间分为内核空间用户空间

  • 内核空间(Ring 0)具有最高权限,可以直接访问所有资源。
  • 用户空间(Ring 3)只能访问受限资源,不能直接访问内存等硬件设备,必须通过系统调用陷入到内核中,才能访问这些特权资源。

进程上下文切换和系统调用的区别
进程是由内核来管理和调度的,进程的切换只能发生在内核态。所以,进程的上下文不仅包括了虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的状态
系统调用过程中,并不涉及到虚拟内存等进程用户态的资源,也不会切换进程。

  • 进程上下文切换,是指从一个进程切换到另一个进程进行。
  • 系统调用过程中一直是同一个进程在运行。

因此,进程的上下文切换比系统调用时多了一步:在保存当前进程的内核状态和CPU寄存器之前,需要先把该进程的虚拟内存、栈等保存下来;而加载了下一个进程的内核态后,还需要刷新进程的虚拟内存和用户栈。

进程上下文切换.png

什么时候会切换进程上文

  • 进程执行终止,它之前使用的 CPU 会释放出来,这时再从就绪队列里,拿一个新的进程过来运行。
  • 当某个进程的时间片耗尽了,就会被系统挂起,切换到其他正在等待 CPU 的进程进行
  • 进程在系统资源不足(比如内存不足)时,等到资源满足后才可以运行,这个时候进程也会被挂起,并由系统调度其他进程运行。
  • 当进程通过睡眠函数 sleep 这样的方法将自己主动挂起时,自然也会重新调度。
  • 当有优先级更高的进程运行时,为了保证高优先级进程的运行,当前进程会被挂起,由高优先级进程来运行。
  • 发生硬件中断时,CPU 上的进程会被中断挂起,转而执行内核中的中断程序服务。

2. 线程上下文切换

线程和进程的区别

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

线程的上下文切换两种情况

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

3. 中断上下文切换

中断处理会打断进程的正常调度和执行。在打断其他进程时,需要将进程当前的状态保存下来,中断结束后,进程仍然可以从原来的状态恢复运行。

进程上下文切换和中断上下文切换的区别

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

进程上下文切换和中断上文切换的相同之处

  • 都需要消耗CPU,切换次数过多会耗费大量 CPU,甚至严重降低系统的整体性能。

4. CPU 上下文切换小结

  • CPU 上下文切换,是保证 Linux 系统正常工作的核心功能之一,一般情况下不需要我们特别关注。
  • 但过多的上下文切换,会把 CPU 时间消耗在寄存器、内核栈以及虚拟内存等数据的保存和恢复上,从而缩短进程真正运行的时间,导致系统的整体性能大幅下降。

如何查看系统的上下文切换情况

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

    • cs (context switch):每秒上下文切换的次数。
    • in (interrupt):每秒中断的次数。
    • r (Running or Runnable) :就绪队列的长度,也就是正在运行和等待 CPU 的进程数。
    • b (Blocked):处在不可中断睡眠状态的进程数。
# 每隔 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 5014224 290736 2060812    0    0    33    37  100  327  6  1 92  0  0
 1  0      0 5014204 290736 2060844    0    0     0     0  321  926  4  1 95  0  0

注:vmstat 只给出了系统总体的上下文切换情况

  • pidstat -w:查看每个进程的上下文切换情况
    关注两列内容:

    • cswch:每秒自愿上下文切换(voluntary context switches) 的次数。
    • nvcswch:每秒非自愿上下文切换(non voluntary context switches) 的次数。
      概念:
    • 自愿上下文切换:进程无法获取所需资源,导致的上下文切换。比如, I/O、内存等系统资源不足时。
    • 非自愿上下文切换:进程由于时间片已到等原因,被系统强制调度,进而发生的上下文切换。比如,大量进程都在争抢 CPU 时。
# 每隔 5 秒输出 1 组数据
$ pidstat -w 5
Linux 4.15.0-42-generic (c5220056-VirtualBox)   12/26/2018  _x86_64_    (2 CPU)

05:47:58 AM   UID       PID   cswch/s nvcswch/s  Command
05:48:03 AM     0         7      0.20      0.00  ksoftirqd/0
05:48:03 AM     0         8     15.57      0.00  rcu_sched
05:48:03 AM     0        11      0.20      0.00  watchdog/0
05:48:03 AM     0        14      0.20      0.00  watchdog/1

注:pidstat默认显示进程的指标数据,加上 -t 参数后,才会输出线程的指标。

  • /proc/interrupts:查看中断发生的类型
# -d 参数表示高亮显示变化的区域
$ watch -d cat /proc/interrupts

根据上下文切换的类型做具体分析

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

CPU 使用率

查看/proc/stat,提供的是系统的 CPU 和任务统计信息。

  • 第一行没有编号的 cpu 表示的是所有 CPU 的累加
  • 其他列则表示不同场景下 CPU 的累加节拍数,单位是USER_HZ, 也就是 10 ms(1/100秒)
$ cat /proc/stat |grep ^cpu
cpu  3177 0 5996 34551 917 0 191 0 0 0
cpu0 1582 0 2980 17287 472 0 76 0 0 0
cpu1 1594 0 3016 17264 445 0 114 0 0 0

CPU使用率相关的重要指标

  • user(us),代表用户态 CPU 时间。
  • nice(ni),代表低优先级用户态 CPU 时间,也就是进程的 nice 值被调整为 1-19 之间时的 CPU 时间。nice 可取值范围是 -20 到 19, 数值越大,优先级反而越低
  • system (sys),代表内核态 CPU 时间。
  • idle(us),代表空闲时间。注意,这里它不包括等待 I/O 的时间(iowait)。
  • iowait(wa),代表等待 I/O 的 CPU 时间。
  • irq(hi),代表处理硬中断的 CPU 时间
  • softirq(si),代表处理软中断的CPU 时间。
  • steal(st),代表当系统运行在虚拟机中的时候,被其他虚拟机占用的 CPU 时间。
  • guest(guest),代表通过虚拟化运行其他操作系统的时间,也就是运行虚拟机的 CPU 时间
  • giest_nice(gnice),代表以低优先级运行虚拟机的时间。

CPU使用率的计算

CPU使用率的计算公式.png

查看 CPU 使用率

  • top:显示系统总体的CPU 和内存使用情况,以及各个进程的资源使用情况。
  • ps:只显示了每个进程的资源使用情况。

top 的输出:

# 默认每 3 秒刷新一次
top - 14:15:36 up  1:05,  1 user,  load average: 0.27, 0.22, 0.15
Tasks: 248 total,   1 running, 179 sleeping,   0 stopped,   0 zombie
%Cpu(s):  1.2 us,  0.7 sy,  0.0 ni, 98.1 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  8168764 total,  6196696 free,   810812 used,  1161256 buff/cache
KiB Swap:  2095100 total,  2095100 free,        0 used.  7016500 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                                     
 8455 c5220056  20   0 1395968 229828  81832 S   3.0  2.8   4:44.39 compiz                                                                                                      
 6739 root      20   0  472636 106196  35576 S   1.7  1.3   0:30.13 Xorg                                                                                                        
 8751 c5220056  20   0  595952  35720  28220 S   0.7  0.4   0:01.41 gnome-terminal-                                                                                             
 6033 root      20   0  568528  68736  39208 S   0.3  0.8   0:11.10 dockerd                                                                                                     
10547 c5220056  20   0   49020   3868   3132 R   0.3  0.0   0:00.02 top                                                                                                         
    1 root      20   0  185428   6020   3968 S   0.0  0.1   0:04.82 systemd                                                                                                     
    2 root      20   0       0      0      0 S   0.0  0.0   0:00.03 kthreadd                                                                                                    
    4 root       0 -20       0      0      0 I   0.0  0.0   0:00.00 kworker/0:0H                                                                                                
    6 root       0 -20       0      0      0 I   0.0  0.0   0:00.00 mm_percpu_wq                                                                                                
    7 root      20   0       0      0      0 S   0.0  0.0   0:00.31 ksoftirqd/0                                                                                                 
    8 root      20   0       0      0      0 I   0.0  0.0   0:01.22 rcu_sched                                                                                                   
    9 root      20   0       0      0      0 I   0.0  0.0   0:00.00 rcu_bh                                                                                                      
   10 root      rt   0       0      0      0 S   0.0  0.0   0:00.03 migration/0                                                                                                 
   11 root      rt   0       0      0      0 S   0.0  0.0   0:00.01 watchdog/0                                                                                                  
   12 root      20   0       0      0      0 S   0.0  0.0   0:00.00 cpuhp/0   
   ...
  • pidstat
# 每隔 1 秒输出一组数据,共输出 2 组
$ pidstat 1 2
Linux 4.15.0-43-generic (c5220056-VirtualBox)   12/27/2018  _x86_64_    (2 CPU)

02:17:31 PM   UID       PID    %usr %system  %guest    %CPU   CPU  Command
02:17:32 PM     0      6739    0.99    1.98    0.00    2.97     0  Xorg
02:17:32 PM  1000      8455    8.91    0.00    0.00    8.91     1  compiz
02:17:32 PM  1000      8751    0.00    0.99    0.00    0.99     1  gnome-terminal-
02:17:32 PM  1000     10558    0.00    0.99    0.00    0.99     1  pidstat

02:17:32 PM   UID       PID    %usr %system  %guest    %CPU   CPU  Command
02:17:33 PM     0      6739    0.00    4.00    0.00    4.00     1  Xorg
02:17:33 PM  1000      8455   11.00    2.00    0.00   13.00     0  compiz
02:17:33 PM  1000      8751    2.00    0.00    0.00    2.00     1  gnome-terminal-
02:17:33 PM  1000     10558    0.00    1.00    0.00    1.00     1  pidstat

Average:      UID       PID    %usr %system  %guest    %CPU   CPU  Command
Average:        0      6739    0.50    2.99    0.00    3.48     -  Xorg
Average:     1000      8455    9.95    1.00    0.00   10.95     -  compiz
Average:     1000      8751    1.00    0.50    0.00    1.49     -  gnome-terminal-
Average:     1000     10558    0.00    1.00    0.00    1.00     -  pidstat

查看占用CPU的是代码里的哪个函数

  • perf top 实时展示系统性能
    • Overhead:是该符号的性能事件在所有采样中的比例,用百分比来表示。
    • Shared:该函数或指令所在的动态共享对象(Dynamic Shared Object),如内核、进程名、动态链接库名、内核模块名等。
    • Object:动态共享对象的类型。比如[.] 表示用户空间的可执行程序、或者动态链接库,而[k]则表示内核空间。
    • Symbol:函数名。当函数名未知时,用十六进制的地址来表示。
    Samples: 2K of event 'cycles:ppp', Event count (approx.): 1330995812
    Overhead  Shared Object                       Symbol
       9.20%  [kernel]                            [k] module_get_kallsym
       3.96%  [kernel]                            [k] vsnprintf
       2.73%  [kernel]                            [k] format_decode
       2.73%  perf                                [.] 0x00000000001ea373
       1.89%  [kernel]                            [k] number
       1.88%  [kernel]                            [k] kallsyms_expand_symbol.constprop.1
       1.69%  perf                                [.] 0x00000000001f6368
       1.69%  [kernel]                            [k] memcpy_erms
       1.43%  perf                                [.] 0x00000000001f77d0
       1.34%  libc-2.27.so                        [.] __libc_calloc
       1.24%  [kernel]                            [k] string
  • perf record
    离线保存系统的性能信息,按Ctrl+C终止采样
  • perf report
    解析perf record保存的采样信息
    Samples: 3K of event 'cycles:ppp', Event count (approx.): 1778811422
    Overhead  Command          Shared Object                  Symbol                            
      28.61%  swapper          [kernel.kallsyms]              [k] intel_idle                    
       4.44%  Xorg             [kernel.kallsyms]              [k] pci_conf1_read                
       2.35%  kworker/1:2      [kernel.kallsyms]              [k] _nv029827rm                   
       2.05%  Xorg             [kernel.kallsyms]              [k] _nv029827rm                   
       1.76%  deepin-wm        [kernel.kallsyms]              [k] pci_conf1_read                
       1.61%  deepin-wm        [kernel.kallsyms]              [k] _nv029827rm                   
       1.09%  irq/150-nvidia   [kernel.kallsyms]              [k] _nv029827rm                   
       1.08%  deepin-wm        [kernel.kallsyms]              [k] syscall_return_via_sysret     
       0.91%  swapper          [unknown]                      [k] 0000000000000000              
       0.71%  Xorg             [kernel.kallsyms]              [k] _nv018294rm                   
       0.41%  swapper          [kernel.kallsyms]              [k] update_load_avg               
       0.33%  Xorg             [vdso]                         [.] 0x0000000000000977
  • 一般使用的时候会加上-g参数以开启调用关系的采样
    Samples: 36K of event 'cycles:ppp', Event count (approx.): 7290912532
      Children      Self  Shared Object                                        Symbol           
    -   15.23%     1.57%  [unknown]                                            [k] 0000000000000000                                                                            
       - 7.58% 0                                                                                
    +   12.19%     0.14%  [kernel]                                            [k] entry_SYSCALL_64_after_hwframe                                                              
    -   12.01%     0.28%  [kernel]                                            [k] do_syscall_64
       - 3.03% do_syscall_64                                                                    
    -    8.65%     0.16%  [kernel]                                            [k] do_idle      
       - 1.75% do_idle                                                                          
    +    4.22%     0.02%  [kernel]                                            [k] call_cpuidle 
    +    4.09%     0.22%  [kernel]                                            [k] cpuidle_enter_state                                                                         
    +    4.01%     0.02%  [kernel]

进程 PID 在变的原因

  • 第一个原因,进程在不停的崩溃重启,比如因为段错误、配置错误等等,这时,进程在退出后可能又被监控系统自动重启了
  • 第二个原因,这些进程都是短时进程,也就是在其他应用内部通过exec调用的外面命令。这些命令一般都只运行很短的时间就会结束,你很难用 top 这种间隔时间比较长的工具发现。

查找一个进程的父进程

  • pstree
$ pstree | grep stress
        |-docker-containe-+-php-fpm-+-php-fpm---sh---stress
        |         |-3*[php-fpm---sh---stress---stress]

小结

  • 用户 CPU 和 Nice CPU 高,说明用户态进程占用了较多的CPU,所以应该着重排查进程的性能问题。
  • 系统 CPU 高,说明内核态占用了较多的 CPU,所以应该着重排查内核线程或者系统调用的性能问题。
  • I/O 等待 CPU 高,说明等待 I/O 的时间比较长,所以应该着重排查系统存储是不是出现了I/O问题。
  • 软中断和硬中断高,说明软中断或硬中断的处理程序占用了较多的CPU,所以应该着重排查内核中的中断服务程序。
  • 碰到常规问题无法解释的 CPU 使用率情况时,首先要想到有肯呢个是短时应用导致的问题,比如
    • 第一,应用里直接调用了其他二进制程序,这些程序通常运行时间比较短,通过 top 等工具也不容易发现。
    • 第二,应用本身在不停地崩溃重启,而启动过程的资源初始化,很可能会占用相当多的 CPU。

不可中断进程和僵尸进程

进程状态

  • RRunningRunnable的缩写,表示进程在 CPU 的就绪队列中,正在运行或者正在等待运行。
  • DDisk Sleep 的缩写,也就是不可中断状态睡眠(Uninterruptible Sleep),一般表示进程正在跟硬件交互,并且交互过程不允许被其他进程中断打断。
  • ZZombie的缩写,表示僵尸进程,也就是进程实际上已经结束了,但是父进程还没有回收它的资源(比如进程的描述符、PID等)。
  • S:Interruptible Sleep的缩写,也就是可中断状态睡眠,表示进程因为等待某个事件而被系统挂起。
  • IIdle的缩写,也就是空闲状态,用在不可中断睡眠的内核线程上。
    T/tStoppedTraced的缩写,表示进程处于暂停或者跟踪状态。
  • XDead的缩写,表示进程已经消亡,所以你不会在 top 或者 ps 命令中看到它。

正常情况下,当一个进程创建了子进程后,它应该通过系统调用 wait() 或者 waitpid()等待子进程结束,回收子进程的资源;而子进程在结束时,会向它的父进程发送 SIGCHLD 信号,所以,父进程还可以注册SIGCHLD 信号的处理函数,异步回收资源。
如果父进程没这么快,或是子进程执行太快,父进程还没来得及处理子进程状态,子进程就已经提前退出,那这时的子进程就会变成僵尸进程。
通常,僵尸进程持续的时间都比较短,在父进程回收它的资源后就会消亡;或者在父进程退出后,由init进程回收后也会消亡。
一旦父进程没有处理子进程的终止,还一直保持运行状态,那么子进程就会一直处于僵尸状态。

进程组和会话

  • 进程组:表示一组相互关联的进程,比如每个子进程都是父进程所在组的成员。
  • 会话:指共享一个控制中断的一个或多个进程组。

找到僵尸进程父进程

  • pstree
pstree -aps 10183
systemd,1 splash
  └─dockerd,5962 -H fd://
      └─docker-containe,6151 --config /var/run/docker/containerd/containerd.toml
          └─docker-containe,10026 -namespace moby -workdir...
              └─app,10055
                  └─(app,10183)

小结

  • 不可中断状态,表示进程正在跟硬件交互,为了保护进程数据和硬件的一致性,系统不允许其他进程或中断打断这个进程。进程长时间处于不可中断状态,通常表示系统有 I/O 性能问题。
  • 僵尸进程表示进程已经退出,但它的父进程还没有回收子进程占用的资源。短暂的僵尸状态我们通常不必理会,但进程长时间处于僵尸状态,就应该注意了,可能有应用程序没有正常处理子进程的退出。
  • iowait 高不一定代表 I/O 有性能瓶颈。当系统中只有 I/O 类型的进程在运行时,iowait 也会很高,但实际上,磁盘的读写远没有达到性能瓶颈的程度。
  • 碰到 iowait 升高时,需要先用 dstat、pidstat 等工具,确认是不是磁盘 I/O 的问题,然后再找是哪些进程导致了 I/O。等待 I/O 的进程一般是不可中断状态,所以用 ps 命令找到 D 状态(即不可中断状态)的进程,多为可疑进程。

Linux 软中断

中断是一种异步的事件处理机制,可以提高系统的并发处理能力。中断处理程序会打断其他进程的运行,为了减少对正常进程运行调度的影响,中断处理程序就需要尽可能快地运行。

Linux 将中断处理过程分成了两个阶段,也就是上半部和下半部

  • 上半部用来快速处理中断,它在中断禁止模式下运行,主要处理跟硬件紧密相关的或时间敏感的工作。
  • 下半部用来延时处理上半部未完成的工作,通常以内核线程的方式运行。

查看软中断和内核线程
proc文件系统是一种内核空间和用户空间进行通信的机制,可以用来查看内核的数据结构,或者用来动态修改内核的配置。

  • /proc/softirqs提供了软中断的运行情况。
  • /proc/interrupts提供了硬中断的运行情况。

在查看/proc/softirqs时,要特别注意:

# cat /proc/softirqs 
                    CPU0       CPU1       
          HI:          0          0
       TIMER:     121964     112390
      NET_TX:        518         13
      NET_RX:        437       3263
       BLOCK:     263737     361157
    IRQ_POLL:          0          0
     TASKLET:         48       1811
       SCHED:      65984      49594
     HRTIMER:          0          0
         RCU:      80938      78628
  • 注意软中断的类型,也就是第一列内容。
  • 注意同一种软中断在不同 CPU 上的分步情况,也就是同一行内容。
  • 软中断实际上是以内核线程的方式运行的,每个 CPU 都对应一个软中断内核先后才能,这个软中断内核线程就叫做 ksoftirqd/CPU 编号。

查看线程运行状况

  • ps
# ps aux|grep softirq
root         7  1.5  0.0      0     0 ?        S    04:25   0:45 [ksoftirqd/0]
root        16  1.6  0.0      0     0 ?        S    04:25   0:47 [ksoftirqd/1]

检查网络接收的软中断

  • sar:用来查看系统的网络收发情况。不仅可以观察网络收发的吞吐量(BPS,每秒收发的字节数),还可以观察网络收发的PPS,即每秒收发的网络帧数。
# -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

第一列:表示报告的时间
第二列:IFACE 表示网卡
第三、四列:rxpck/stxpck/s 分别表示每秒接收、发送的网络帧数,也就是PPS。
第五、六列:rxkB/stxkB/s 分别表示每秒接收、发送的千字节数,也就是 BPS

  • tcpdump
# -i eth0 只抓取 eth0 网卡,-n 不解析协议名和主机名
# tcp port 80 表示只抓取 tcp 协议并且端口号为 80 的网络帧
$ tcpdump -i eth0 -n tcp port 80
15:11:32.678966 IP 192.168.0.2.18238 > 192.168.0.30.80: Flags [S], seq 458303614, win 512, length 0
...

如何快速分析出系统 CPU 的瓶颈

CPU 性能指标

1. CPU 使用率
CPU使用率描述了非空闲时间占总 CPU 时间的百分比,根据 CPU 上运行任务的不同,又被分为用户 CPU、系统 CPU、等待 I/O CPU、软中断和硬中断等。

  • 用户 CPU 使用率,包括用户态 CPU 使用率(user) 和低优先级用户态 CPU 使用率(nice),表示 CPU 在用户态运行的时间百分比。用户 CPU 使用率高,通常说明有应用程序比较繁忙。
  • 系统 CPU 使用率,表示 CPU 在内核态运行的时间百分比(不包括中断)。系统 CPU 使用率高,说明内核比较繁忙。
  • 等待 I/O 的 CPU 使用率,通常也称为 iowait,表示等待 I/O 的时间百分比。iowait 高,通常说明系统与硬件设备的 I/O 交互时间比较长。
  • 软中断和硬中断的 CPU 使用率,分别表示内核调用软中断处理程序、硬中断处理程序的时间百分比。它们的使用率高,通常说明系统发生了大量的中断。
  • 除了上面这些,还有在虚拟环境中会用到的窃取 CPU 使用率(steal)和用户 CPU 使用率(guest),分别表示被其他虚拟机占用的 CPU 时间百分比,和运行客户虚拟机的 CPU 时间百分比。

2. 平均负载(Load Average)
平均负载,也就是系统的平均活跃进程数,它反映了系统的整体负载情况,主要包括三个数值,分别指过去 1 分钟、过去 5 分钟和过去 15 分钟的平均复制子。
理想情况下,平均负载等于逻辑 CPU 个数,这表示每个 CPU 都恰好被充分利用。如果平均负载大于逻辑 CPU 个数,就表示负载比较重了。

3.进程上下文切换

  • 无法获取资源而导致的自愿上下文切换。
  • 被系统强制调度导致的非自愿上下文切换。

4.CPU 缓存的命中率

由于 CPU 发展的速度远快于内存的发展, CPU 的处理速度就比内存的访问速度快得多。这样,CPU 在访问内存的时候,免不了要等待内存的响应。为了协调这两者巨大的性能差距,CPU缓存(通常是多级缓存)就出现了。

根据不断增长的热点数据,这些缓存按照大小不同分为L1、L2、L3等三级缓存,其中 L1 和 L2 常用在单核中,L3则用在多核中。
从 L1 到 L3,三级缓存的大小依次增大,相应的,性能依次降低(当然比内存还是好得多)。而它们的命中率,衡量的是 CPU 缓存的复用情况,命中率越高,则表示性能越好。

CPU性能指标思维导图

性能工具

第一个维度:从 CPU 的性能指标出发
从 CPU 的性能指标出发。也就是说,当你要查看某个性能指标时,要清楚知道哪些工具可以做到。

第二个维度:从工具出发

从工具出发。也就是当你已经安装了某个工具后,要知道这个工具能提供哪些指标。

快速分析 CPU 的性能瓶颈

想弄清楚性能指标的关联性,就要通晓每个性能指标的工作原理。
为了缩小排查范围,我通常会先运行几个支持指标较多的工具,如topvmstatpidstat

CPU 性能优化的几个思路

怎么评估性能优化的效果

三步走:

  1. 确定性能的量化指标
  2. 测试优化前的性能指标。
  3. 测试优化后的性能指标。

多个性能问题同时存在,要怎么选择?

并不是所有的性能问题都值得优化。
第一,如果发现是系统资源达到了瓶颈,比如CPU使用率达到了 100%,那么首先优化的一定是系统资源使用问题。完成系统资源瓶颈的优化后,我们吃爱要考虑其他问题。
第二,针对不同类型的指标,首先去优化哪些由瓶颈导致的,性能指标变化幅度最大的问题。比如产生瓶颈后,用户 CPU 使用率升高了10%,而系统系统CPU使用率却升高了50%,这个时候就应该首先优化系统 CPU 使用率。

有多种优化方法时,要如何选择?

性能优化并非没有成本。性能优化通常会带来复杂度的提升,降低程序的可维护性,还可能在优化一个指标时,引发其他指标的异常。

CPU 优化

应用程序优化
从应用程序的角度来说,降低 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上。

 

行走的code
行走的code

要发表评论,您必须先登录