进程/线程上下文切换会用掉你多少CPU?

进程是操做系统的伟大发明之一,对应用程序屏蔽了CPU调度、内存管理等硬件细节,而抽象出一个进程的概念,让应用程序专心于实现本身的业务逻辑既可,并且在有限的CPU上能够“同时”进行许多个任务。可是它为用户带来方便的同时,也引入了一些额外的开销。以下图,在进程运行中间的时间里,虽然CPU也在忙于干活,可是却没有完成任何的用户工做,这就是进程机制带来的额外开销。 php

file

在进程A切换到进程B的过程当中,先保存A进程的上下文,以便于等A恢复运行的时候,可以知道A进程的下一条指令是啥。而后将要运行的B进程的上下文恢复到寄存器中。这个过程被称为上下文切换。上下文切换开销在进程很少、切换不频繁的应用场景下问题不大。可是如今Linux操做系统被用到了高并发的网络程序后端服务器。在单机支持成千上万个用户请求的时候,这个开销就得拿出来讲道说道了。由于用户进程在请求Redis、Mysql数据等网络IO阻塞掉的时候,或者在进程时间片到了,都会引起上下文切换。 nginx

file

一个简单的进程上下文切换开销测试实验

废话很少说,咱们先用个实验测试一下,到底一次上下文切换须要多长的CPU时间!实验方法是建立两个进程并在它们之间传送一个令牌。其中一个进程在读取令牌时就会引发阻塞。另外一个进程发送令牌后等待其返回时也处于阻塞状态。如此往返传送必定的次数,而后统计他们的平均单次切换时间开销。redis

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

每次执行的时间会有差别,屡次运行后平均每次上下文切换耗时3.5us左右。固然了这个数字因机器而异,并且建议在实机上测试。 sql

前面咱们测试系统调用的时候,最低值是200ns。可见,上下文切换开销要比系统调用的开销要大。系统调用只是在进程内将用户态切换到内核态,而后再切回来,而上下文切换但是直接从进程A切换到了进程B。显然这个上下文切换须要完成的工做量更大。后端

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

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

直接开销就是在切换时,cpu必须作的事情,包括:服务器

  • 一、切换页表全局目录
  • 二、切换内核态堆栈
  • 三、切换硬件上下文(进程恢复前,必须装入寄存器的数据统称为硬件上下文)网络

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

间接开销主要指的是虽然切换到一个新进程后,因为各类缓存并不热,速度运行会慢一些。若是进程始终都在一个CPU上调度还好一些,若是跨CPU的话,以前热起来的TLB、L一、L二、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是致使上下文切换频繁的元凶

file


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


个人公众号是「开发内功修炼」,在这里我不是单纯介绍技术理论,也不仅介绍实践经验。而是把理论与实践结合起来,用实践加深对理论的理解、用理论提升你的技术实践能力。欢迎你来关注个人公众号,也请分享给你的好友~~~

相关文章
相关标签/搜索