Linux

上下文切换小结及其带来的性能问题

一 什么是/为什么上下文切换?

1什么是上下文切换?

多个进程不停的轮流执行CPU,上下文切换是指CPU在执行完某个进程之后,需要切换到下一个进程来执行,或者当前进程申请的CPU时间片已经用完,到点儿了,轮到下一个进程来使用CPU资源了的情况下,CPU把当前进程的相关信息,比如CPU寄存器、程序计数器program counter、虚拟内存等信息暂时保存起来,转而去恢复和读取下一个进程的上下文信息,然后执行下一个进程,当下一个进程执行完之后,再来执行这个进程的时候,恢复这些刚刚保存起来的信息。

2为什么发生上下文切换?

我们看到的系统是在高并发同时执行不同的任务,其实系统内部将CPU资源分成多个时间片,让进程根据优先级高低,等待CPU时间长短来确定哪个进程来轮流使用CPU资源的。即内部通过一种快速高效的来调度进程执行,进而是用户感觉到系统是在高并发执行的,内部的这种高效切换和执行的效率实在是太高了,以至于用户或者我们根本感觉不到这种轮流切换的发生。

3为什么上下文切换不好?

因为进程申请到的可以执行的CPU时间有限,一旦把本来就不多的CPU时间,用于保存,恢复上下文的话,剩下的可以给进程真正用于执行任务的时间就不多了。所以,频繁的上下文切换,会导致性能下降。说白了,就是花费了大量的时间用于无用功上面。

4什么时候会发生上下文切换?

进程的CPU时间片用完、申请的系统资源没有获取到(比如内存不够)、进程状态转变(运行、就绪、阻塞)、sleep时间到、被硬件中断打断等。

二上下文切换的分类

1 进程上下文切换
2 线程上下文切换
3 中断上下文切换

通常来讲,如果是同一个进程下的多线程上下文切换,则线程上下文切换的开销要小于进程上下文切换。

三 动手实践线程上下文切换

1 如何查看上下文切换信息
vmstat:

vmstat 1;每秒输出一次数据,持续输出。

[root@localhost ~]# 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 1887908   2108 1849896    0    0     1     1   30   32  0  0 100  0  0
 6  0      0 1887940   2108 1849896    0    0     0    24 45507 1495326 15 83  2  0  0
 8  0      0 1887908   2108 1849896    0    0     0     0 46715 1539506 15 84  2  0  0
 8  0      0 1887908   2108 1849896    0    0     0     0 52043 1428535 14 84  2  0  0
 6  0      0 1887908   2108 1849896    0    0     0     0 48989 1501183 15 83  2  0  0
  • r:running+runnable队列,正在运行和等待CPU的进程数;
  • b(Blocked):处于不可被中断的进程队列,就是top里看到的状态为D的进程
  • in: interrupts每秒中断数;
  • cs:context switch,发生上下文切换的数据;
pidstat:

pidstat -wtu 1 ; 每秒输出一次数据,-w表示输出进程切换指标,-u表示输出CPU使用指标,-t表示的是把线程上下文切换也显示出来。

[root@localhost ~]# pidstat -wtu  1
Linux 3.10.0-862.el7.x86_64 (localhost.localdomain)     2021年08月11日  _x86_64_        (2 CPU)
​
09时18分12秒   UID      TGID       TID    %usr %system  %guest    %CPU   CPU  Command
09时18分13秒     0      3022         -   26.47  100.00    0.00  100.00     0  sysbench
09时18分13秒     0         -      3023    1.96   16.67    0.00   18.63     1  |__sysbench
09时18分13秒     0         -      3024    1.96   17.65    0.00   19.61     0  |__sysbench
09时18分13秒     0         -      3025    2.94   15.69    0.00   18.63     1  |__sysbench
09时18分13秒     0         -      3026    1.96   17.65    0.00   19.61     0  |__sysbench
09时18分13秒     0         -      3027    1.96   16.67    0.00   18.63     1  |__sysbench
09时18分13秒     0         -      3028    2.94   17.65    0.00   20.59     0  |__sysbench
09时18分13秒     0         -      3029    2.94   15.69    0.00   18.63     0  |__sysbench
09时18分13秒     0         -      3030    1.96   16.67    0.00   18.63     0  |__sysbench
09时18分13秒     0         -      3031    2.94   17.65    0.00   20.59     1  |__sysbench
09时18分13秒     0         -      3032    3.92   14.71    0.00   18.63     0  |__sysbench
09时18分13秒     0      3079         -    0.98    0.98    0.00    1.96     1  pidstat
09时18分13秒     0         -      3079    0.98    0.98    0.00    1.96     1  |__pidstat
​
09时18分12秒   UID      TGID       TID   cswch/s nvcswch/s  Command
09时18分13秒     0         9         -     12.75      0.00  rcu_sched
09时18分13秒     0         -         9     12.75      0.00  |__rcu_sched
09时18分13秒     0        11         -      0.98      0.00  watchdog/0
09时18分13秒     0         -        11      0.98      0.00  |__watchdog/0
09时18分13秒     0        12         -      0.98      0.00  watchdog/1
09时18分13秒     0         -        12      0.98      0.00  |__watchdog/1
09时18分13秒     0        14         -      4.90      0.00  ksoftirqd/1
09时18分13秒     0         -        14      4.90      0.00  |__ksoftirqd/1
09时18分13秒     0       396         -     19.61      0.00  xfsaild/dm-0
09时18分13秒     0         -       396     19.61      0.00  |__xfsaild/dm-0
09时18分13秒     0         -      1295      0.98      0.00  |__tuned
09时18分13秒     0         -       895      0.98      0.00  |__in:imjournal
09时18分13秒     0      1316         -      0.98      0.00  kworker/0:1
09时18分13秒     0         -      1316      0.98      0.00  |__kworker/0:1
09时18分13秒     0      1393         -      0.98      0.00  sshd
09时18分13秒     0         -      1393      0.98      0.00  |__sshd
09时18分13秒  1000      1455         -      0.98      0.00  postgres
09时18分13秒  1000         -      1455      0.98      0.00  |__postgres
09时18分13秒     0      3006         -      2.94      0.00  kworker/1:2
09时18分13秒     0         -      3006      2.94      0.00  |__kworker/1:2
09时18分13秒     0         -      3023  14951.96 124602.94  |__sysbench
09时18分13秒     0         -      3024  16162.75 139048.04  |__sysbench
09时18分13秒     0         -      3025  20060.78 114410.78  |__sysbench
09时18分13秒     0         -      3026  19034.31 115395.10  |__sysbench
09时18分13秒     0         -      3027  18244.12 119304.90  |__sysbench
09时18分13秒     0         -      3028  24889.22 134348.04  |__sysbench
09时18分13秒     0         -      3029  23888.24 120276.47  |__sysbench
09时18分13秒     0         -      3030  23119.61 124440.20  |__sysbench
09时18分13秒     0         -      3031  11955.88 139916.67  |__sysbench
09时18分13秒     0         -      3032  25288.24 130357.84  |__sysbench
09时18分13秒     0      3055         -      0.98      0.00  vmstat
09时18分13秒     0         -      3055      0.98      0.00  |__vmstat
09时18分13秒     0      3079         -      0.98      1.96  pidstat
09时18分13秒     0         -      3079      0.98      1.96  |__pidstat
  • cswch/s:voluntary context switch自愿上下文切换,通常由于进程申请不到内存资源,或者进程等待I/O完成,但是I/O迟迟完成不了,发生了等待,这是进程自愿发生的切换;
  • ncswch/s: non voluntary context switch,非自愿上下文切换,通常由于进程申请的CPU时间片使用完了,轮流到其它进程使用CPU资源的场景,即多进程竞争CPU的场景。此时,进程不得不被迫的发生切换。即进程被内核调度产生了上下文切换,所以叫做非自愿上下文切换。
2 动手实践线程上下文切换

系统环境,跑在虚拟机上的2核4G的CentOS Linux release 7.5.1804:

[root@localhost ~]# cat /etc/redhat-release 
CentOS Linux release 7.5.1804 (Core) 
[root@localhost ~]# free -m
              total        used        free      shared  buff/cache   available
Mem:           3790         132        1558         747        2099        2305
Swap:          3967           0        3967
[root@localhost ~]# lscpu
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                2
On-line CPU(s) list:   0,1
Thread(s) per core:    1
Core(s) per socket:    1
座:                 2
NUMA 节点:         1
厂商 ID:           AuthenticAMD
CPU 系列:          16
型号:              8
型号名称:        Six-Core AMD Opteron(tm) Processor 8431
步进:              0
CPU MHz:             2394.000
BogoMIPS:            4788.00
超管理器厂商:  VMware
虚拟化类型:     完全
L1d 缓存:          64K
L1i 缓存:          64K
L2 缓存:           512K
L3 缓存:           5118K
NUMA 节点0 CPU:    0,1
Flags:                 fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm 3dnowext 3dnow constant_tsc art rep_good nopl tsc_reliable nonstop_tsc pni cx16 x2apic popcnt hypervisor lahf_lm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw retpoline_amd ibp_disable vmmcall
[root@localhost ~]# 
  • session1:

运行 sysbench ,模拟系统多线程调度的瓶颈

[root@localhost ~]# sysbench --threads=10 --max-time=300 threads run
WARNING: --max-time is deprecated, use --time instead
sysbench 1.0.20 (using bundled LuaJIT 2.1.0-beta2)
​
Running the test with following options:
Number of threads: 10
Initializing random number generator from current time
​
​
Initializing worker threads...
​
Threads started!
​
  • session2:

运行vmstat 1,查看进程上下文切换信息:

[root@localhost ~]# 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
 1  0      0 1592800   2108 2147788    0    0     0     3    5    4  0  0 100  0  0
 0  0      0 1592684   2108 2147808    0    0     0    20   85   72  0  0 100  0  0
 9  0      0 1591044   2108 2147836    0    0     0     0 11126 330961  3 20 77  0  0
 6  0      0 1590764   2108 2147836    0    0     0     0 49923 1544918 15 84  2  0  0
 7  0      0 1590796   2108 2147844    0    0     0     0 48061 1495074 13 86  2  0  0
 6  0      0 1590764   2108 2147844    0    0     0     0 49301 1512222 14 84  2  0  0
 6  0      0 1590764   2108 2147844    0    0     0     0 49262 1485962 15 82  3  0  0
 7  0      0 1590764   2108 2147844    0    0     0     0 48399 1533651 12 86  2  0  0
^C
[root@localhost ~]# 

从上,看到cpu的r队列(已经运行CPU的和等待CPU的队列),已经满负荷在跑了,2个CPU的机器,r队列达到7个左右了。同时,system的中断in和进程上下文切换cs已经突然暴涨了。同时,CPU使用率指标上,us+sy已经逼近100%了,同时系统CPU使用率达到84%多了,说明CPU资源主要被内核占用了。

  • session3:

pidstat -utw 1:查看进程的上下文切换-w;CPU使用率-u,进程的线程上下文切换-t,1表示每间隔1秒输出1次结果,并且是持续输出,直到ctrl+c结束。

[root@localhost ~]# pidstat -wut 1
Linux 3.10.0-862.el7.x86_64 (localhost.localdomain)     2021年08月15日  _x86_64_        (2 CPU)
​
11时49分42秒   UID      TGID       TID    %usr %system  %guest    %CPU   CPU  Command
11时49分43秒     0     16325         -   24.51  100.00    0.00  100.00     1  sysbench
11时49分43秒     0         -     16326    1.96   15.69    0.00   17.65     1  |__sysbench
11时49分43秒     0         -     16327    3.92   15.69    0.00   19.61     1  |__sysbench
11时49分43秒     0         -     16328    3.92   18.63    0.00   22.55     0  |__sysbench
11时49分43秒     0         -     16329    2.94   16.67    0.00   19.61     0  |__sysbench
11时49分43秒     0         -     16330    2.94   18.63    0.00   21.57     1  |__sysbench
11时49分43秒     0         -     16331    2.94   15.69    0.00   18.63     1  |__sysbench
11时49分43秒     0         -     16332    1.96   18.63    0.00   20.59     0  |__sysbench
11时49分43秒     0         -     16333    1.96   18.63    0.00   20.59     1  |__sysbench
11时49分43秒     0         -     16334    0.98   16.67    0.00   17.65     1  |__sysbench
11时49分43秒     0         -     16335    1.96   15.69    0.00   17.65     0  |__sysbench
11时49分43秒     0     16372         -    0.98    0.98    0.00    1.96     0  pidstat
11时49分43秒     0         -     16372    0.98    0.98    0.00    1.96     0  |__pidstat
​
11时49分42秒   UID      TGID       TID   cswch/s nvcswch/s  Command
11时49分43秒     0         3         -      2.94      0.00  ksoftirqd/0
11时49分43秒     0         -         3      2.94      0.00  |__ksoftirqd/0
11时49分43秒     0         9         -      9.80      0.00  rcu_sched
11时49分43秒     0         -         9      9.80      0.00  |__rcu_sched
11时49分43秒     0       396         -     19.61      0.00  xfsaild/dm-0
11时49分43秒     0         -       396     19.61      0.00  |__xfsaild/dm-0
11时49分43秒     0         -       635      0.98      0.00  |__gmain
11时49分43秒     0         -      1295      0.98      0.00  |__tuned
11时49分43秒     0         -       895      0.98      0.00  |__in:imjournal
11时49分43秒     0     14154         -      0.98      0.00  kworker/0:0
11时49分43秒     0         -     14154      0.98      0.00  |__kworker/0:0
11时49分43秒     0     16236         -      0.98      0.00  kworker/1:0
11时49分43秒     0         -     16236      0.98      0.00  |__kworker/1:0
11时49分43秒     0         -     16326  21296.08 132564.71  |__sysbench
11时49分43秒     0         -     16327  12213.73 149473.53  |__sysbench
11时49分43秒     0         -     16328  10559.80 126749.02  |__sysbench
11时49分43秒     0         -     16329  20205.88 120006.86  |__sysbench
11时49分43秒     0         -     16330  15943.14 125617.65  |__sysbench
11时49分43秒     0         -     16331  19605.88 119339.22  |__sysbench
11时49分43秒     0         -     16332  22058.82 106377.45  |__sysbench
11时49分43秒     0         -     16333  24408.82 126319.61  |__sysbench
11时49分43秒     0         -     16334  27084.31 104035.29  |__sysbench
11时49分43秒     0         -     16335  24101.96 105334.31  |__sysbench
11时49分43秒     0     16372         -      0.98      3.92  pidstat
11时49分43秒     0         -     16372      0.98      3.92  |__pidstat
  • session4:

查看中断情况:watch -d ‘cat /proc/interrupts | sort -nr -k 2’

[root@localhost ~]# watch -d 'cat /proc/interrupts | sort -nr -k 2'
Every 2.0s: cat /proc/interrupts | sort -nr -k 2                                                                                             Sun Aug 15 11:55:33 2021
​
RES:   37946619   38347726   Rescheduling interrupts
LOC:   31751877   32825938   Local timer interrupts
 14:     914353         13   IO-APIC-edge      ata_piix
 58:     866870     636678   PCI-MSI-edge      ens192-rxtx-1
IWI:     365984     382747   IRQ work interrupts
TLB:      91030      92900   TLB shootdowns
 56:      67176     143747   PCI-MSI-edge      vmw_pvscsi
CAL:      57724      48417   Function call interrupts
MCP:       3121       3121   Machine check polls
PMI:        525        594   Performance monitoring interrupts
NMI:        525        594   Non-maskable interrupts

这里,看到虚拟文件系统/proc下的中断也在明显升高,且变化最明显的是RES,重调度中断,该中断是唤醒空闲状态的CPU来调度新的任务去执行,用于多处理器(SMP)系统中,调度器用于分散不同任务到不同CPU执行的机制,也叫处理器间中断(Inter-Processor Interrupts)。

当然,此时的top看到系统CPU资源利用率非常高:

[root@localhost ~]# top
top - 09:46:08 up 29 days, 23:00,  3 users,  load average: 3.36, 0.95, 0.36
Tasks: 127 total,   1 running, 126 sleeping,   0 stopped,   0 zombie
%Cpu0  : 11.4 us, 86.6 sy,  0.0 ni,  2.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  : 11.0 us, 87.7 sy,  0.0 ni,  1.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  3881344 total,  2634024 free,   269276 used,   978044 buff/cache
KiB Swap:  4063228 total,  4063228 free,        0 used.  2782968 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                                                                                  
 1198 root      20   0   87344   4480   3332 S 195.3  0.1   1:35.74 sysbench --threads=10 --max-time=300 threads run                                                                                                         
    1 root      20   0  125616   4100   2536 S   0.0  0.1   1:19.63 /usr/lib/systemd/systemd --switched-root --system --deserialize 22                                                                                       
    2 root      20   0       0      0      0 S   0.0  0.0   0:01.49 [kthreadd]                                                                                                                                               
    3 root      20   0       0      0      0 S   0.0  0.0   0:20.05 [ksoftirqd/0]                                                                                                                                            
    5 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 [kworker/0:0H]                                                                                                                                           
    7 root      rt   0       0      0      0 S   0.0  0.0   0:03.36 [migration/0]                                                                                                                                            
    8 root      20   0       0      0      0 S   0.0  0.0   0:00.00 [rcu_bh]                                                                                                                                                 
    9 root      20   0       0      0      0 S   0.0  0.0  35:39.50 [rcu_sched]                                                                                                                                              
   10 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 [lru-add-drain]                                                                                                                                          
   11 root      rt   0       0      0      0 S   0.0  0.0   0:22.55 [watchdog/0]                                                                                                                                             
   12 root      rt   0       0      0      0 S   0.0  0.0   0:20.39 [watchdog/1]                                                                                                                                             
   13 root      rt   0       0      0      0 S   0.0  0.0   0:03.41 [migration/1]                                                                                                                                            
   14 root      20   0       0      0      0 S   0.0  0.0   0:12.13 [ksoftirqd/1]                                                                                                                                            
   16 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 [kworker/1:0H]                                                                                                                                           
   18 root      20   0       0      0      0 S   0.0  0.0   0:00.00 [kdevtmpfs]  

四小结

vmstat查看CPU的r,b队列,CPU利用率,系统的中断in和进程上下文切换指标cs;

pidstat -wut 1; 每隔1秒持续输出进程上下文切换、CPU利用率,以及线程上下文切换指标。

通常如果:

自愿上下文切换过高,说明进程在等待资源,比如可能出现了IO等待问题,导致进程资源上下文切换;

非自愿上下文切换过高,说明进程不断被系统调度,也就是说进程在争抢CPU资源,说明当前情况下CPU成了瓶颈;

中断变高,意味着CPU被中断处理程序占用,结合/proc/interruptes来分析中断类型;

五参考

https://linuxtechlab.com/benchmark-linux-systems-install-sysbench-tool/

https://www.howtoforge.com/how-to-benchmark-your-system-cpu-file-io-mysql-with-sysbench

https://towshif.github.io/site/tutorials/Linux%20Shell/benchmark-linux/

留言