乐趣区

关于linux:进程线程上下文切换会用掉你多少CPU

过程是操作系统的平凡创造之一,对应用程序屏蔽了 CPU 调度、内存治理等硬件细节,而形象出一个过程的概念,让应用程序分心于实现本人的业务逻辑既可,而且在无限的 CPU 上能够“同时”进行许多个工作。然而它为用户带来不便的同时,也引入了一些额定的开销。如下图,在过程运行两头的工夫里,尽管 CPU 也在忙于干活,然而却没有实现任何的用户工作,这就是过程机制带来的额定开销。

在过程 A 切换到过程 B 的过程中,先保留 A 过程的上下文,以便于等 A 复原运行的时候,可能晓得 A 过程的下一条指令是啥。而后将要运行的 B 过程的上下文复原到寄存器中。这个过程被称为上下文切换。上下文切换开销在过程不多、切换不频繁的利用场景下问题不大。然而当初 Linux 操作系统被用到了高并发的网络程序后端服务器。在单机反对成千上万个用户申请的时候,这个开销就得拿进去说道说道了。因为用户过程在申请 Redis、Mysql 数据等网络 IO 阻塞掉的时候,或者在过程工夫片到了,都会引发上下文切换。

一个简略的过程上下文切换开销测试试验

废话不多说,咱们先用个试验测试一下,到底一次上下文切换须要多长的 CPU 工夫!试验办法是创立两个过程并在它们之间传送一个令牌。其中一个过程在读取令牌时就会引起阻塞。另一个过程发送令牌后期待其返回时也处于阻塞状态。如此往返传送肯定的次数,而后统计他们的均匀单次切换工夫开销。

# gcc main.c -o main
# ./main./main
Before Context Switch Time1565352257 s, 774767 us
After Context SWitch Time1565352257 s, 842852 us

每次执行的工夫会有差别,屡次运行后 均匀每次上下文切换耗时 3.5us 左右。当然了这个数字因机器而异,而且倡议在实机上测试。

后面咱们测试零碎调用的时候,最低值是 200ns。可见,上下文切换开销要比零碎调用的开销要大。零碎调用只是在过程内将用户态切换到内核态,而后再切回来,而上下文切换可是间接从过程 A 切换到了过程 B。显然这个上下文切换须要实现的工作量更大。

过程上下文切换开销都有哪些

那么上下文切换的时候,CPU 的开销都具体有哪些呢?开销分成两种,一种是间接开销、一种是间接开销。

间接开销就是在切换时,cpu 必须做的事件,包含:

  • 1、切换页表全局目录
  • 2、切换内核态堆栈
  • 3、切换硬件上下文(过程复原前,必须装入寄存器的数据统称为硬件上下文)

    • ip(instruction pointer):指向以后执行指令的下一条指令
    • bp(base pointer): 用于寄存执行中的函数对应的栈帧的栈底地址
    • sp(stack poinger): 用于寄存执行中的函数对应的栈帧的栈顶地址
    • cr3: 页目录基址寄存器,保留页目录表的物理地址
    • ……
  • 4、刷新 TLB
  • 5、系统调度器的代码执行

间接开销次要指的是尽管切换到一个新过程后,因为各种缓存并不热,速度运行会慢一些。如果过程始终都在一个 CPU 上调度还好一些,如果跨 CPU 的话,之前热起来的 TLB、L1、L2、L3 因为运行的过程曾经变了,所以以局部性原理 cache 起来的代码、数据也都没有用了,导致新过程穿透到内存的 IO 会变多。其实咱们下面的试验并没有很好地测量到这种状况,所以理论的上下文切换开销可能比 3.5us 要大。

想理解更具体操作过程的同学请参考《深刻了解 Linux 内核》中的第三章和第九章。

一个更为业余的测试工具 -lmbench

lmbench 用于评估零碎综合性能的多平台开源 benchmark,可能测试包含文档读写、内存操作、过程创立销毁开销、网络等性能。应用办法简略,但就是跑有点慢,感兴趣的同学能够本人试一试。
这个工具的劣势是是进行了多组试验,每组 2 个过程、8 个、16 个。每个过程应用的数据大小也在变,充沛模仿 cache miss 造成的影响。我用他测了一下后果如下:

-------------------------------------------------------------------------
Host                 OS  2p/0K 2p/16K 2p/64K 8p/16K 8p/64K 16p/16K 16p/64K  
                         ctxsw  ctxsw  ctxsw ctxsw  ctxsw   ctxsw   ctxsw  
--------- ------------- ------ ------ ------ ------ ------ ------- -------  
bjzw_46_7 Linux 2.6.32- 2.7800 2.7800 2.7000 4.3800 4.0400 4.75000 5.48000  

lmbench 显示的过程上下文切换耗时从 2.7us 到 5.48 之间。

线程上下文切换耗时

后面咱们测试了过程上下文切换的开销,咱们再持续在 Linux 测试一下线程。看看到底比过程能不能快一些,快的话能快多少。

在 Linux 下其实本并没有线程,只是为了投合开发者口味,搞了个轻量级过程进去就叫做了线程。轻量级过程和过程一样,都有本人独立的 task_struct 过程描述符,也都有本人独立的 pid。从操作系统视角看,调度上和过程没有什么区别,都是在期待队列的双向链表里抉择一个 task_struct 切到运行态而已。只不过轻量级过程和一般过程的区别是能够共享同一内存地址空间、代码段、全局变量、同一关上文件汇合而已。

同一过程下的线程之所有 getpid()看到的 pid 是一样的,其实 task_struct 里还有一个 tgid 字段。对于多线程程序来说,getpid()零碎调用获取的实际上是这个 tgid,因而附属同一过程的多线程看起来 PID 雷同。

咱们用一个试验来测试一下,其原理和过程测试差不多,创立了 20 个线程,在线程之间通过管道来传递信号。接到信号就唤醒,而后再传递信号给下一个线程,本人睡眠。这个试验里独自思考了给管道传递信号的额定开销,并在第一步就统计了进去。

# gcc -lpthread main.c -o main
0.508250  
4.363495  

每次试验后果会有一些差别,下面的后果是取了屡次的后果之后而后均匀的,大概每次线程切换开销大概是 3.8us 左右。从上下文切换的耗时上来看,Linux 线程(轻量级过程)其实和过程差异不太大

Linux 相干命令

既然咱们晓得了上下文切换比拟的耗费 CPU 工夫,那么咱们通过什么工具能够查看一下 Linux 里到底在产生多少切换呢?如果上下文切换曾经影响到了零碎整体性能,咱们有没有方法把有问题的过程揪出来,并把它优化掉呢?

# 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  
 2  0      0 595504   5724 190884    0    0   295   297    0    0 14  6 75  0  4  
 5  0      0 593016   5732 193288    0    0     0    92 19889 29104 20  6 67  0  7  
 3  0      0 591292   5732 195476    0    0     0     0 20151 28487 20  6 66  0  8  
 4  0      0 589296   5732 196800    0    0   116   384 19326 27693 20  7 67  0  7  
 4  0      0 586956   5740 199496    0    0   216    24 18321 24018 22  8 62  0  8  

或者是

# sar -w 1  
proc/s  
     Total number of tasks created per second.  
cswch/s  
     Total number of context switches per second.  
11:19:20 AM    proc/s   cswch/s  
11:19:21 AM    110.28  23468.22  
11:19:22 AM    128.85  33910.58  
11:19:23 AM     47.52  40733.66  
11:19:24 AM     35.85  30972.64  
11:19:25 AM     47.62  24951.43  
11:19:26 AM     47.52  42950.50  
......   

上图的环境是一台生产环境机器,配置是 8 核 8G 的 KVM 虚机,环境是在 nginx+fpm 的,fpm 数量为 1000,均匀每秒解决的用户接口申请大概 100 左右。其中 cs 列 示意的就是在 1s 内零碎产生的上下文切换次数,大概 1s 切换次数都达到 4W 次了。粗略估算一下,每核大概每秒须要切换 5K 次,则 1s 内须要花将近 20ms 在上下文切换上。要晓得这是虚机,自身在虚拟化上还会有一些额定开销,而且还要真正耗费 CPU 在用户接口逻辑解决、零碎调用内核逻辑解决、以及网络连接的解决以及软中断,所以 20ms 的开销实际上不低了。

那么进一步,咱们看下到底是哪些过程导致了频繁的上下文切换?

# pidstat -w 1  
11:07:56 AM       PID   cswch/s nvcswch/s  Command
11:07:56 AM     32316      4.00      0.00  php-fpm  
11:07:56 AM     32508    160.00     34.00  php-fpm  
11:07:56 AM     32726    131.00      8.00  php-fpm  
......  

因为 fpm 是同步阻塞的模式,每当申请 Redis、Memcache、Mysql 的时候就会阻塞导致 cswch/ s 被迫上下文切换,而只有工夫片到了之后才会触发 nvcswch/ s 非被迫切换。可见 fpm 过程大部分的切换都是被迫的、非被迫的比拟少。

如果想查看具体某个过程的上下文切换总状况,能够在 /proc 接口下间接看,不过这个是总值。

grep ctxt /proc/32583/status  
voluntary_ctxt_switches:        573066  
nonvoluntary_ctxt_switches:     89260  

本节论断

上下文切换具体做哪些事件咱们没有必要记,只须要记住一个论断既可,测得作者开发机 上下文切换的开销大概是 2.7-5.48us 左右,你本人的机器能够用我提供的代码或工具进行一番测试。
lmbench 绝对更精确一些,因为思考了切换后 Cache miss 导致的额定开销。

扩大:平时大家在操作系统实践学习的时候都晓得 CPU 工夫片的概念,工夫片到了会将过程从 CPU 上赶下来,换另一个过程上。但其实在咱们互联网的网络 IO 密集型的利用里,真正因为工夫片到了而产生的非被迫切换很少,绝大部分都是因为期待网络 IO 而进行的被迫切换。下面的例子你也能够看出,我的一个 fpm 过程被动切换有 57W 次,而被动切换只有不到 9W 次。所以,在同步阻塞的开发模式里,网络 IO 是导致上下文切换频繁的首恶



开发内功修炼之 CPU 篇专辑:

  • 1. 你认为你的多核 CPU 都是真核吗?多核“假象”
  • 2. 据说你只知内存,而不知缓存?CPU 示意很伤心!
  • 3.TLB 缓存是个神马鬼,如何查看 TLB miss?
  • 4. 过程 / 线程切换到底须要多少开销?
  • 5. 协程到底比线程牛在什么中央?
  • 6. 软中断会吃掉你多少 CPU?
  • 7. 一次零碎调用开销到底有多大?
  • 8. 一次简略的 php 申请 redis 会有哪些开销?
  • 9. 函数调用太多了会有性能问题吗?

我的公众号是「开发内功修炼」,在这里我不是单纯介绍技术实践,也不只介绍实践经验。而是把实践与实际联合起来,用实际加深对实践的了解、用实践进步你的技术实际能力。欢送你来关注我的公众号,也请分享给你的好友~~~

退出移动版