过程是操作系统的平凡创造之一,对应用程序屏蔽了 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. 函数调用太多了会有性能问题吗?
我的公众号是「开发内功修炼」,在这里我不是单纯介绍技术实践,也不只介绍实践经验。而是把实践与实际联合起来,用实际加深对实践的了解、用实践进步你的技术实际能力。欢送你来关注我的公众号,也请分享给你的好友~~~