[转帖]Linux性能优化实战 —— CPU

linux,性能,优化,实战,cpu · 浏览次数 : 0

小编点评

## CPU 性能指标 **1. 用户 CPU 使用率 (user)** * 用户态 CPU 使用率 (user):表示用户态运行的时间百分比。 * 低优先级用户态 CPU 使用率 (nice):表示低优先级用户态运行的时间百分比。 **2. 系统 CPU 使用率 (system)** * 内核态 CPU 使用率:表示内核态运行的时间百分比。 * 系统 CPU 使用率:表示内核比较繁忙。 **3. 等待 I/O CPU 使用率 (wait)** * 等待 I/O 的 CPU 使用率:表示等待 I/O 的时间百分比。 **4. 软中断和硬中断 CPU 使用率** * 软中断使用率:表示内核调用软中断处理程序的时间百分比。 * 硬中断使用率:表示内核调用硬中断处理程序的时间百分比。 **5. 平均负载 (Load Average)** * 平均负载:表示系统的平均活跃进程数。 * 过去 1 分钟、过去 5 分钟和过去 15 分钟的平均负载:分别指过去 1 分钟、过去 5 分钟和过去 15 分钟的平均复制子数量。 **6. 进程上下文切换** * 被系统强制调度导致的非自愿上下文切换:非自愿上下文切换是指系统强制调度程序进行上下文切换,导致其他程序无法获取 CPU 使用资源。 **7. CPU 缓存命中率** * CPU 缓存的命中率衡量的是 CPU 缓存复用情况,越高,则表示性能越好。 **8. CPU 性能优化的思路** * 确定性能的量化指标测试优化前的性能指标。 * 测试优化后的性能指标。 * 选择性能问题并进行优化。

正文

https://www.jianshu.com/p/2b6658ad59b3

 

Linux性能分析概要

1. 性能指标

 

 
linux性能指标.png

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

 

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

2. linux性能工具图谱

 
linux性能工具图谱.png

3. linux性能优化思维导图

 
linux性能优化思维导图.png

平均负载

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 上下文.png

 

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

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

1. 进程上下文切换

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

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


     
    Linux特权等级空间.png

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

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

因此,进程的上下文切换比系统调用时多了一步:在保存当前进程的内核状态和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。

不可中断进程和僵尸进程

进程状态

  • R :RunningRunnable的缩写,表示进程在 CPU 的就绪队列中,正在运行或者正在等待运行。
  • DDisk Sleep 的缩写,也就是不可中断状态睡眠(Uninterruptible Sleep),一般表示进程正在跟硬件交互,并且交互过程不允许被其他进程中断打断。
  • ZZombie的缩写,表示僵尸进程,也就是进程实际上已经结束了,但是父进程还没有回收它的资源(比如进程的描述符、PID等)。
  • S:Interruptible Sleep的缩写,也就是可中断状态睡眠,表示进程因为等待某个事件而被系统挂起。
  • IIdle的缩写,也就是空闲状态,用在不可中断睡眠的内核线程上。
    -T/tStopped 或 Traced的缩写,表示进程处于暂停或者跟踪状态。
  • 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/s 和 txpck/s 分别表示每秒接收、发送的网络帧数,也就是PPS。
第五、六列:rxkB/s 和 txkB/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缓存(通常是多级缓存)就出现了。

 
CPU 缓存.png

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

 

CPU性能指标思维导图

 
CPU性能指标

 

性能工具

第一个维度:从 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上。

与[转帖]Linux性能优化实战 —— CPU相似的内容:

[转帖]Linux性能优化实战 —— CPU

https://www.jianshu.com/p/2b6658ad59b3 Linux性能分析概要 1. 性能指标 linux性能指标.png 随着应用负载的增加,系统资源的使用也会升高,甚至达到极限。而性能问题的本质,就是系统资源已经达到瓶颈,但请求的处理却还不够快,无法支撑更多的请求。性能分析

[转帖]《Linux性能优化实战》笔记(二)—— CPU 上下文切换(上)

上一篇的最后一个例子,在多个进程竞争CPU时,我们看到每个进程实际上%usr部分只有20%多,70%多是在wait,但是load远远高于单个进程使用CPU达到100%。 这让我想到之前看的RWP公开课,里面有一篇连接池管理。为什么相同的业务量,起6千个连接(进程)远远要慢于200个连接,因为绝大多数

[转帖]《Linux性能优化实战》笔记(七)—— CPU瓶颈快速分析及性能优化思路

相当于是前面篇章的小结 一、 CPU 性能指标 常见指标包括: 平均负载CPU 使用率(user、iowait、system、软硬中断等)进程上下文切换(自愿、非自愿)CPU 缓存的命中率 CPU 的处理速度就比内存的访问速度快得多。这样,CPU 在访问内存的时候,免不了要等待内存的响应。为了协调这

[转帖]《Linux性能优化实战》笔记(四)—— CPU 使用率

一、 节拍率与CPU时间 前一篇说到,Linux 作为一个多任务操作系统,将每个 CPU 的时间划分为很短的时间片,再通过调度器轮流分配给各个任务使用,因此造成多任务同时运行的错觉。 为了维护 CPU 时间,Linux 通过事先定义的节拍率(内核中表示为 HZ),触发时间中断,并使用全局变量 Jif

[转帖]《Linux性能优化实战》笔记(三)—— CPU 上下文切换(下)

上篇介绍了三种CPU 上下文切换以及它们可能造成的问题和原因,这一篇来看看在系统中如何发现CPU 上下文切换问题。 一、 查看上下文切换情况 主要使用两个命令:vmstat以及之前用过的pidstat。 1. vmstat # 每隔5秒输出1组数据vmstat 5 procs memory swap

[转帖]《Linux性能优化实战》笔记(23)—— 内核线程 CPU 利用率过高,perf 与 火焰图

在排查网络问题时,我们还经常碰到的一个问题,就是内核线程的 CPU 使用率很高。比如,在高并发的场景中,内核线程 ksoftirqd 的 CPU 使用率通常就会比较高。回顾一下前面学过的 CPU 和网络模块,你应该知道,这是网络收发的软中断导致的。 要分析 ksoftirqd 这类 CPU 使用率比

[转帖]《Linux性能优化实战》笔记(五)—— 不可中断进程与僵尸进程

一、 进程状态 1. 状态含义 从 ps或者 top 命令的输出中,可以看到处于不同状态的进程 R:Running 或 Runnable,表示进程在 CPU 的就绪队列中,正在运行或者正在等待运行D:Disk Sleep,不可中断状态睡眠(Uninterruptible Sleep)S:Interr

[转帖]《Linux性能优化实战》笔记(一)—— 平均负载

最近在看极客时间的《Linux性能优化实战》课程,记录下学习内容。 一、 平均负载(Load Average) 1. 概念 我们都知道uptime命令的最后三列分别是过去 1 分钟、5 分钟、15 分钟系统的平均负载,到底平均负载是什么? 简单来说,平均负载是指单位时间内,系统处于可运行状态和不可中

[转帖]《Linux性能优化实战》笔记(八)—— 内存是怎么工作的

一、 内存映射 我们通常所说的内存容量,指的是物理内存。物理内存也称为主存,大多数计算机用的主存都是动态随机访问内存(DRAM)。只有内核才可以直接访问物理内存。那么,进程要访问内存时,该怎么办呢? Linux 内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的。这样,进程就可以

[转帖]《Linux性能优化实战》笔记(22)—— 网络丢包问题分析

所谓丢包,是指在网络数据的收发过程中,由于种种原因,数据包还没传输到应用程序中,就被丢弃了。这些被丢弃包的数量,除以总的传输包数,也就是我们常说的丢包率。丢包率是网络性能中最核心的指标之一。丢包通常会带来严重的性能下降,特别是对 TCP 来说,丢包通常意味着网络拥塞和重传,进而还会导致网络延迟增大、