你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

进程上下文切换开销

[复制链接]
gaosmile 发布时间:2020-12-2 21:18
进程是我们开发同学非常熟悉的概念,我们可能也听说过进程上下文切换开销。那么今天让我们来思考一个问题,究竟一次进程上下文切换会吃掉多少CPU时间呢?线程据说比进程轻量,它的上下文切换会比进程切换节约很多CPU时间吗?带着这些疑问,让我们进入正题。
1进程以及进程切换进程是操作系统的伟大发明之一,对应用程序屏蔽了CPU调度、内存管理等硬件细节,而抽象出一个进程的概念,让应用程序专心于实现自己的业务逻辑既可,而且在有限的CPU上可以“同时”进行许多个任务。但是它为用户带来方便的同时,也引入了一些额外的开销。如下图,在进程运行中间的时间里,虽然CPU也在忙于干活,但是却没有完成任何的用户工作,这就是进程机制带来的额外开销。
& X' r, M* |3 t& W* c
微信图片_20201202211733.jpg
在进程A切换到进程B的过程中,先保存A进程的上下文,以便于等A恢复运行的时候,能够知道A进程的下一条指令是啥。然后将要运行的B进程的上下文恢复到寄存器中。这个过程被称为上下文切换。上下文切换开销在进程不多、切换不频繁的应用场景下问题不大。但是现在Linux操作系统被用到了高并发的网络程序后端服务器。在单机支持成千上万个用户请求的时候,这个开销就得拿出来说道说道了。因为用户进程在请求Redis、Mysql数据等网络IO阻塞掉的时候,或者在进程时间片到了,都会引发上下文切换。

2 x, W, u8 J' @
微信图片_20201202211737.jpg

' b/ l1 A. ]$ F7 `! x6 D
2一次简单的进程切换开销测试废话不多说,我们先用个实验测试一下,到底一次上下文切换需要多长的CPU时间!实验方法是创建两个进程并在它们之间传送一个令牌。其中一个进程在读取令牌时就会引起阻塞。另一个进程发送令牌后等待其返回时也处于阻塞状态。如此往返传送一定的次数,然后统计他们的平均单次切换时间开销。编译、运行
$ P. Q4 ^# R7 ~' E" s" M$ i
# gcc main.c -o main& H7 o/ j) a" s- q
# ./main./main
" S- n9 d8 l/ `" {/ aBefore Context Switch Time1565352257 s, 774767 us
/ e/ V5 R* `' M+ UAfter Context SWitch Time1565352257 s, 842852 us
9 ?& U1 B' V# E) G. }
3 j5 m$ x$ F) S2 l
每次执行的时间会有差异,多次运行后平均每次上下文切换耗时3.5us左右。当然了这个数字因机器而异,而且建议在实机上测试。前文我们测试系统调用的时候,最低值是200ns。可见,上下文切换开销要比系统调用的开销要大。系统调用只是在进程内将用户态切换到内核态,然后再切回来,而上下文切换可是直接从进程A切换到了进程B。显然这个上下文切换需要完成的工作量更大。3进程切换开销分析那么上下文切换的时候,CPU的开销都具体有哪些呢?开销分成两种,一种是直接开销、一种是间接开销。
, \8 ~+ D* ^  V: I
直接开销就是在切换时,cpu必须做的事情,包括:1、切换页表全局目录2、切换内核态堆栈3、切换硬件上下文(进程恢复前,必须装入寄存器的数据统称为硬件上下文)
6 D$ h! y9 S9 N! k* B7 `; _+ t
  • ip(instruction pointer):指向当前执行指令的下一条指令
  • bp(base pointer): 用于存放执行中的函数对应的栈帧的栈底地址
  • sp(stack poinger): 用于存放执行中的函数对应的栈帧的栈顶地址
  • cr3:页目录基址寄存器,保存页目录表的物理地址
  • ......

    " G8 I* W  ^* `4 \3 q+ y$ D/ c$ n
  Z4 @* y7 E2 X& p& b" b
4、刷新TLB5、系统调度器的代码执行1 N9 f* C: h2 z. s' w1 p0 j
间接开销主要指的是虽然切换到一个新进程后,由于各种缓存并不热,速度运行会慢一些。如果进程始终都在一个CPU上调度还好一些,如果跨CPU的话,之前热起来的TLB、L1、L2、L3因为运行的进程已经变了,所以以局部性原理cache起来的代码、数据也都没有用了,导致新进程穿透到内存的IO会变多。其实我们上面的实验并没有很好地测量到这种情况,所以实际的上下文切换开销可能比3.5us要大。想了解更详细操作过程的同学请参考《深入理解Linux内核》中的第三章和第九章。4一个更为专业的测试工具-lmbenchlmbench用于评价系统综合性能的多平台开源benchmark,能够测试包括文档读写、内存操作、进程创建销毁开销、网络等性能。使用方法简单,但就是跑有点慢,感兴趣的同学可以自己试一试。这个工具的优势是是进行了多组实验,每组2个进程、8个、16个。每个进程使用的数据大小也在变,充分模拟cache miss造成的影响。我用他测了一下结果如下(下图需要横向滚动查看完全):
( A) H6 X4 p, s6 {3 I; F! a
-------------------------------------------------------------------------
. E1 _$ K& ^: v8 _) J2 X, }* NHost                 OS  2p/0K 2p/16K 2p/64K 8p/16K 8p/64K 16p/16K 16p/64K. Z0 |) L; k: n0 e+ ~5 Q, S
                         ctxsw  ctxsw  ctxsw ctxsw  ctxsw   ctxsw   ctxsw* b' }5 _* r9 ?9 l
--------- ------------- ------ ------ ------ ------ ------ ------- -------
6 ~$ B# R3 [- ^3 M3 p$ j: Zbjzw_46_7 Linux 2.6.32- 2.7800 2.7800 2.7000 4.3800 4.0400 4.75000 5.48000
% u/ r# T. `* h1 ]7 M

2 X  d2 x( c9 B1 p2 {
lmbench显示的进程上下文切换耗时从2.7us到5.48之间。5线程上下文切换耗时前面我们测试了进程上下文切换的开销,我们再继续在Linux测试一下线程。看看究竟比进程能不能快一些,快的话能快多少。在Linux下其实本并没有线程,只是为了迎合开发者口味,搞了个轻量级进程出来就叫做了线程。轻量级进程和进程一样,都有自己独立的task_struct进程描述符,也都有自己独立的pid。从操作系统视角看,调度上和进程没有什么区别,都是在等待队列的双向链表里选择一个task_struct切到运行态而已。只不过轻量级进程和普通进程的区别是可以共享同一内存地址空间、代码段、全局变量、同一打开文件集合而已。
1 q  Z3 Z# P* l2 _4 k% Z2 @# B
同一进程下的线程之所有getpid()看到的pid是一样的,其实task_struct里还有一个tgid字段。对于多线程程序来说,getpid()系统调用获取的实际上是这个tgid,因此隶属同一进程的多线程看起来PID相同。
4 a* p' t' b; L
我们用一个实验来进行另外一个测试。其原理和进程测试差不多,创建了20个线程,在线程之间通过管道来传递信号。接到信号就唤醒,然后再传递信号给下一个线程,自己睡眠。这个实验里单独考虑了给管道传递信号的额外开销,并在第一步就统计了出来。
% ?9 O# F* K6 j7 v* T9 y$ n# q
# gcc -lpthread main.c -o main1 R" D2 g* t* K
0.508250) ]* V8 z4 c" r1 F, |' `9 x1 N
4.363495
7 I- l+ e# b  A) y  M7 K

3 A0 j$ a  T' m, ?. {
每次实验结果会有一些差异,上面的结果是取了多次的结果之后然后平均的,大约每次线程切换开销大约是3.8us左右。从上下文切换的耗时上来看,Linux线程(轻量级进程)其实和进程差别不太大。6Linux相关命令既然我们知道了上下文切换比较的消耗CPU时间,那么我们通过什么工具可以查看一下Linux里究竟在发生多少切换呢?如果上下文切换已经影响到了系统整体性能,我们有没有办法把有问题的进程揪出来,并把它优化掉呢?(下图需要横向滚动查看完全)
$ r% ]5 Q. K5 |! t
# vmstat 1
9 c7 ~* O- @; I$ k4 Z0 `! b) oprocs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----
& Y1 u+ O# ?0 I0 z+ } r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st8 C, b' V' z; N7 D7 ~7 u, n  ]* G+ k
2  0      0 595504   5724 190884    0    0   295   297    0    0 14  6 75  0  4
$ R" b. V0 B0 r, C& ~ 5  0      0 593016   5732 193288    0    0     0    92 19889 29104 20  6 67  0  7$ @, `( V, s! B8 Q, E( k, X* w
3  0      0 591292   5732 195476    0    0     0     0 20151 28487 20  6 66  0  8
1 e. J* y: B3 c. d 4  0      0 589296   5732 196800    0    0   116   384 19326 27693 20  7 67  0  7
- \% y0 k4 [1 i 4  0      0 586956   5740 199496    0    0   216    24 18321 24018 22  8 62  0  8
3 F: @1 t3 m4 y0 D
. Z5 c/ D' f7 d
或者是

7 w) f) g5 S& Y& r3 }- a- c
# sar -w 1! q6 Y( e& v2 u2 ?# K+ @
proc/s
' O6 E# k; R! I5 R2 `     Total number of tasks created per second.( f" t8 s% Y, s7 r0 U/ {) J
cswch/s; L- V$ l- [! b  Q1 T! d
     Total number of context switches per second." e, Y+ o  j6 i* [$ T2 `8 j
11:19:20 AM    proc/s   cswch/s' H$ x" A$ S% O1 e
11:19:21 AM    110.28  23468.22
' |. n. D6 Q; A* X11:19:22 AM    128.85  33910.58' n! C% x3 o4 _% a* _
11:19:23 AM     47.52  40733.66$ r8 [- [" o! P& i$ _
11:19:24 AM     35.85  30972.64
0 Y) H: e' E, s/ `9 f- k- r8 M& B11:19:25 AM     47.62  24951.43
0 `; ^: i1 z; S: K11:19:26 AM     47.52  42950.50) s0 A( T$ {) c. A8 k3 S) Y* Q
......# f7 U: B6 u2 R, t
  s# R6 y5 F4 r
上图的环境是一台生产环境机器,配置是8核8G的KVM虚机,环境是在nginx+fpm的,fpm数量为1000,平均每秒处理的用户接口请求大约100左右。其中cs列表示的就是在1s内系统发生的上下文切换次数,大约1s切换次数都达到4W次了。粗略估算一下,每核大约每秒需要切换5K次,则1s内需要花将近20ms在上下文切换上。要知道这是虚机,本身在虚拟化上还会有一些额外开销,而且还要真正消耗CPU在用户接口逻辑处理、系统调用内核逻辑处理、以及网络连接的处理以及软中断,所以20ms的开销实际上不低了。那么进一步,我们看下到底是哪些进程导致了频繁的上下文切换?(下图可能需要横向滚动查看完全)

! K- ]/ G) g6 H, T6 O& P
# pidstat -w 1* U) l9 s: F& E, ?5 q6 J
11:07:56 AM       PID   cswch/s nvcswch/s  Command
; N& z! S. A# T1 x1 D# e11:07:56 AM     32316      4.00      0.00  php-fpm
( c, `/ L) R) y7 o; L( P11:07:56 AM     32508    160.00     34.00  php-fpm
  w( S5 J, b8 j9 i3 @- ?11:07:56 AM     32726    131.00      8.00  php-fpm  d% S# P! B6 B4 \
......3 f: E+ T% e+ C( m

% R! C2 V( a; N# S
由于fpm是同步阻塞的模式,每当请求Redis、Memcache、Mysql的时候就会阻塞导致cswch/s自愿上下文切换,而只有时间片到了之后才会触发nvcswch/s非自愿切换。可见fpm进程大部分的切换都是自愿的、非自愿的比较少。如果想查看具体某个进程的上下文切换总情况,可以在/proc接口下直接看,不过这个是总值。

6 d' A3 a6 w* W4 {9 W
grep ctxt /proc/32583/status
, U: w6 h0 S$ H; z5 k! \- pvoluntary_ctxt_switches:        573066* ?  j# r( k# w4 M+ N4 I0 g
nonvoluntary_ctxt_switches:     89260

0 k) T& t" Z  ^) j1 g4 O+ s" t
7本文结论上下文切换具体做哪些事情我们没有必要记,只需要记住一个结论既可,测得作者开发机上下文切换的开销大约是2.7-5.48us左右,你自己的机器可以用我提供的代码或工具进行一番测试。lmbench相对更准确一些,因为考虑了切换后Cache miss导致的额外开销。
* s8 g, `7 z4 g/ k7 A: K& }
赞 收藏 评论0 发布时间:2020-12-2 21:18

举报

0个回答

所属标签

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版