当前位置: 首页 > Linux

进程-线程上下文切换占用了多少CPU?

时间:2023-04-06 06:19:08 Linux

进程是操作系统的伟大发明之一。它为应用程序屏蔽了CPU调度、内存管理等硬件细节,抽象出进程的概念,使应用程序可以集中精力实现自己的业务逻辑。许多任务可以在有限的CPU上“同时”执行。但是在给用户带来方便的同时,也引入了一些额外的开销。如下图所示,在进程运行过程中,虽然CPU忙于工作,但并没有完成任何用户工作。这是进程机制带来的额外开销。在进程A切换到进程B的过程中,先保存进程A的上下文,这样当A恢复运行时,就可以知道进程A的下一条指令是什么。然后恢复B进程的上下文运行到寄存器中。此过程称为上下文切换。在进程少、切换不频繁的应用场景下,上下文切换的开销不是大问题。但是现在Linux操作系统多用于高并发的网络程序后端服务器。当单台机器支持数千个用户请求时,这个开销就不得不讨论了。因为当用户进程请求Redis、Mysql数据等网络IO块,或者进程时间片up时,会触发上下文切换。一个简单的进程上下文切换开销测试实验废话不多说,下面我们用一个实验来测试一次上下文切换需要多少CPU时间!实验方法是创建两个进程并在它们之间传递令牌。进程之一在读取令牌时阻塞。发送令牌后等待令牌返回时,另一个进程也被阻塞。这样进行一定次数的往返传输,然后统计它们的平均单次切换时间开销。#gccmain.c-omain#./main./mainBeforeContextSwitchTime1565352257s,774767usAfterContextSwitchTime1565352257s,842852us每次执行的时间会有所不同,多次运行后每次上下文切换的平均时间为3.5我们左右。当然这个数字因机而异,建议在真机上测试。我们之前测试系统调用的时候,最低值是200ns。可见,上下文切换的开销要大于系统调用。系统调用只是在进程中将用户态切换到内核态,然后再切换回来,而上下文切换则直接从进程A切换到进程B。显然这个上下文切换需要做更多的工作。进程上下文切换的开销有哪些那么上下文切换时具体的CPU开销有哪些呢?费用有两种,一种是直接费用,一种是间接费用。直接开销是CPU在切换时必须做的,包括:1.切换页表全局目录2.切换内核态栈3.切换硬件上下文(进程恢复前必须加载到寄存器的数据是统称为硬件上下文)ip(指令指针):指向当前正在执行的指令的下一条指令bp(基址指针):用于存放正在执行的函数sp(stackpointer)对应的栈帧的栈底地址):用于存放执行函数对应的栈frame的栈顶地址cr3:页目录基地址寄存器,里面保存的是页目录表的物理地址...4.刷新TLB5,代码间接执行系统调度器的开销主要是指虽然切换到一个新的进程,但是由于各个缓存不热,所以速度会变慢。如果进程总是在一个CPU上调度会更好。如果是跨CPU的话,之前比较热的TLB、L1、L2、L3因为运行的进程发生了变化,所以根据局部性原理缓存的代码和数据也都变了。没用的,导致新进程穿透内存的IO会增加。事实上,我们上面的实验并没有很好地衡量这种情况,所以实际的上下文切换开销可能大于3.5us。想了解更详细操作过程的同学,请参考《深入理解Linux内核》中的第三章和第九章。更专业的测试工具——lmbenchlmbench是一个多平台的开源benchmark,用于评估系统的整体性能,可以测试包括文档读写、内存操作、进程创建和销毁开销、网络等性能。使用方法简单,就是运行起来有点慢。感兴趣的同学可以自行尝试。这个工具的好处是可以进行多组实验,每组有2个过程,8个过程,16个过程。每个进程使用的数据大小也在变化,充分模拟缓存未命中的影响。我和他测试了一下,结果如下:------------------------------------------------------------------主机操作系统2p/0K2p/16K2p/64K8p/16K8p/64K16p/16K16p/64Kctxswctxswctxswctxswctxswctxswctxsw--------------------------------------------------------------bjzw_46_7Linux2.6.32-2.78002.78002.70004.38004.04004.750005.48000lmbench显示进程上下文切换时间从在2.7us和5.48之间。线程上下文切换耗时我们之前测试了进程上下文切换的开销,我们将继续在Linux中测试线程。看看能不能比进程快,能快多少。其实Linux下是没有线程的,只是为了迎合开发者的口味,刚出来的轻量级进程就叫线程。轻量进程和进程一样,有自己独立的task_struct进程描述符,也有自己独立的pid。从操作系统的角度来看,调度和进程没有区别。他们只是在等待队列的双向链表中选择一个task_struct,切换到运行状态。只是轻量级进程与普通进程的区别在于,它可以共享相同的内存地址空间、代码段、全局变量和打开的同一组文件。同一个进程下所有线程的getpid()看到的pid是一样的。其实task_struct中也有一个tgid字段。对于多线程程序来说,getpid()系统调用实际上是获取了这个tgid,所以属于同一个进程的多个线程看起来具有相同的PID。让我们用一个实验来测试一下。原理类似于过程测试。创建20个线程,线程之间通过管道传递信号。收到信号后唤醒,然后将信号传递给下一个线程,自己休眠。在本实验中,单独考虑向管道传输信号的额外开销,在第一步计算。#gcc-lpthreadmain.c-omain0.5082504.363495每次实验的结果会有一些差异。以上结果取多次结果后取平均值。每次线程切换的开销约为3.8us。从上下文切换的耗时角度来看,Linux线程(轻量级进程)与进程没有太大区别。Linux相关命令既然我们知道了上下文切换会消耗更多的CPU时间,那么我们可以使用什么工具来查看Linux中发生了多少次切换呢?如果上下文切换影响了系统的整体性能,我们有没有办法找出问题所在的进程并进行优化?#vmstat1procs----------内存------------swap-------io------system-------cpu-----rbswpdfreebuffcachesisobiboincsussyidwast200595504572419088400295297001467504500593016573219328800092198892910420667073005912925732195476000020151284872066608400589296573219680000116384193262769320767074005869565740199496002162418321240182286208或#sar-w1proc/s每秒创建的任务总数。cswch/s每秒上下文切换的总数。11:19:20AMproc/scswch/s11:19:21AM110.2823468.2211:19:22AM128.8533910.5811:19:23AM47.5240733.6611:19:24AM35.8530972.6411:19:25AM47.6224951.4311:19:26AM47.5242950.50......上图中的环境是生产环境机器,配置是8-core8GKVM虚拟机,环境为nginx+fpm,fpm数量为1000,平均每秒处理的用户界面请求100左右,cs列表示1s内系统发生上下文切换的次数,约1s内的开关次数达到4W次以上。粗略估计,每个核心每秒需要切换约5K次,1s内切换上下文需要近20ms。要知道这是一个虚拟机,虚拟化本身会有一些额外的开销,在用户界面逻辑处理、系统调用内核逻辑处理、网络连接处理、软中断等方面实际上都会消耗CPU,所以开销20ms其实不低。那么更进一步,我们看看哪些进程会导致频繁的上下文切换?#pidstat-w111:07:56AMPIDcswch/snvcswch/sCommand11:07:56AM323164.000.00php-fpm11:07:56AM32508160.0034.00php-fpm11:07:56AM32726131.008.00php-fpm......由于fpm是同步阻塞模式,每当请求Redis、Memcache、Mysql时,都会阻塞导致cswch/s自愿上下文切换,只有时间片up后才会nvcswch/被触发的非自愿切换。可以看出,fpm进程的切换大部分是自愿的,非自愿的相对较少。如果要查看具体进程的整体上下文切换情况,可以直接在/proc界面下查看,但这是总值。grepctxt/proc/32583/statusvoluntary_ctxt_switches:573066nonvoluntary_ctxt_switches:89260在本节的结论中我们不需要记住上下文切换具体做了什么。我们只需要记住一个结论。笔者开发机上上下文切换的开销大概在2.7-5.48us左右,自己的机子可以用我提供的代码或者工具测试一下。lmbench相对更准确,因为它考虑了切换后Cachemiss带来的额外开销。延伸:通常大家在学习操作系统理论的时候都知道CPU时间片的概念。当时间片到了,这个进程就会被赶出CPU,被另一个进程代替。但实际上,在我们互联网的网络IO密集型应用中,真正因为时间片的到来而发生的非自愿切换是非常少的,大部分都是因为等待网络IO而发生的自愿切换。从上面的例子也可以看出,我的一个fpm进程主动切换了57W次,而被动切换只有不到9W次。所以,在同步阻塞的开发模式下,网络IO是造成频繁上下文切换的罪魁祸首。多核“错觉”2、听说你只知道内存,不知道缓存?CPU表示很伤心!3.TLB缓存是个鬼,如何查看TLBmiss?4、进程/线程切换需要多少开销?5、协程比线程好在哪里?6.softirq会吃掉多少CPU?7、一次系统调用的开销是多少?8、一个简单的php请求redis的开销是多少?9、函数调用过多会不会有性能问题?我的公众号是《练内功》。在这里,我不是简单地介绍技术理论,也不是只介绍实践经验。而是理论联系实际,用实践加深对理论的理解,用理论提高技术实践能力。欢迎关注我的公众号,分享给你的朋友吧~~~