总说上下文切换耗性能,那他到底耗了多少性能?

大家好,我是「云舒编程」,今天我们来聊聊上下文切换性能消耗。

文章首发于微信公众号:云舒编程

关注公众号获取:
1、大厂项目分享
2、各种技术原理分享
3、部门内推

一、前言

   众所周知,操作系统是一个分时复用系统,通过将CPU时间分为好几份。系统在很短的时间内,将 CPU 轮流分配给它们,从而实现多任务同时运行的错觉。

   伴随着的还有一个词是上下文切换,无论在工作中还是面试中,我们总会听到要减少线程、进程的上下文切换,因为上下文切换的代价比较高,会影响性能。

    今天我们就来详细说说上下文切换到底在切换什么,以及如何可视化的观察上下文切换的代价,它是怎么影响程序性能的。

二、进程是什么

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。from:百科

直白的说就是假设你去组织一场活动,那么你肯定会需要记录活动需要的物质、人员、时间安排,在什么时间点应该做哪些事情。这些事情你肯定不会单纯记录在脑子里,会找一个文档记录下来。
同理,当一个程序需要运行时,操作系统也需要记录该程序使用了多少内存,打开了什么文件,程序运行到哪里了,这些信息都需要记录下来,而进程就充当了这个角色,也就是百科中说的:“是系统进行资源分配的基本单位”。

2.1、进程资源

更详细些,一个进程会拥有如下资源,其中带*号是每个进程都有独立一份,有了这样的设计结构,多个进程就能并发运行了。
图片

三、上下文切换到底在切换什么?

    有了CPU的时间片和进程后,操作系统就可以将程序执行起来了。假设有三个进程A,B,C。首先是A获得了CPU时间片,待A的时间片结束后,操作系统会挑选B或者C进行执行。  
    那么这里就存在一个问题,A程序可能并没有执行完,这个时候被临时中断了,下一次该怎么执行呢?为了解决这个问题,于是提出了上下文的概念:“进程运行过程中用到的资源,进程的状态,执行到了哪里”。

    在把A切换出去之前,首先把上下文保存下来,这样等到再次执行A的时候就可以从上次执行的状态继续执行,从而达成没有中断过的假象。  

图片
更加详细的解释是:
每个程序运行前,CPU需要知道从哪里加载任务,从哪里开始运行,有哪些指令。而这些都需要CPU寄存器、程序计数器、内存管理单元(MMU)配合完成。

一、寄存器是CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果。寄存器拥有非常高的读写速度,所以在寄存器之间的数据传送非常快。

二、程序计数器:程序计数器是用于存放下一条指令所在单元的地址的地方。当执行一条指令时,首先需要根据PC中存放的指令地址,将指令由内存取到指令寄存器中,此过程称为“取指令”。与此同时,PC中的地址或自动加1或由转移指针给出下一条指令的地址。此后经过分析指令,执行指令。完成第一条指令的执行,而后根据PC取出第二条指令的地址,如此循环,执行每一条指令。

三、内存管理单元(MMU):通过虚拟内存和物理内存的映射,使的每个程序都认为自己可以使用完整的内存。详细解释:https://baike.baidu.com/item/MMU/4542218

上下文切换就是将A进程存储在CPU寄存器,程序计数器,MMU中的数据取出来,然后将B进程的数据放进去。而这些保存下来的上下文,会存储在系统内核中,并在任务重新调度执行时再次加载进来。这样就能保证任务原来的状态不受影响,让任务看起来还是连续运行。

四、有哪些类型的上下文切换

4.1、进程上下文切换

在进程上下文切换过程中,操作系统需要完成以下操作:

  • 保存当前进程的上下文(如寄存器状态、程序计数器等)
  • 加载新进程的上下文
  • 更新内存管理单元(MMU)以映射新进程的地址空间
  • 切换到新进程的执行环境

4.2、线程上下文切换

线程跟进程的区别在于:线程是依赖于进程存在,线程是调度的基本单位,进程为线程提供虚拟内存,全局变量等资源。
简单来说:

  • 当进程只有一个线程时,可以认为进程就等于线程。
  • 当进程拥有多个线程时,这些线程会共享相同的虚拟内存和全局变量等资源。这些资源在上下文切换时是不需要修改的。
  • 另外,线程也有自己的私有数据,比如栈和寄存器等,这些在上下文切换时也是需要保存的。

那么线程上下文切换就可以分为两种情况:

  1. 线程属于同一个进程,由于属于同一个进程,那么虚拟内存等资源不需要切换,只需要切换线程的私有资源,例如栈、寄存器等资源即可。
  2. 线程属于不同进程,这个时候切换过程跟进程上下文切换没有区别。

也就是说,在同一进程内线程上下文切换的代价是比进程切换小的。

4.3、系统调用上下文切换

我们知道,操作系统把进程的运行空间分为内核空间和用户空间。
图片

  • 其中操作系统运行在 内核空间(也叫内核态)(Ring 0)具有最高权限,可以直接访问所有资源;
  • 而用户写的代码运行在 用户空间(也叫用户态)(Ring 3)只能访问受限资源,不能直接访问内存等硬件设备,必须通过系统调用陷入到内核中,然后由内核代码去访问,再把结果返回。

   也就是说进程即可以运行在用户态,也可以运行在内核态,当调用系统函数时就会从用户态转入内核态,调用结束时就会从内核态转入用户态。那么这个转换过程会涉及上下文切换吗?
   答案是肯定的,因为操作系统的代码最终也是需要CPU去执行的,那么肯定需要寄存器和程序计数器的参与,那么就需要把用户写的代码从这两个地方“踢出去”,换成操作系统的代码,等操作完成了又需要把操作系统的代码从这两个地方“踢出去”,换成用户代码。也就是说一次系统调用导致了两次上下文切换。
   但是由于这个时候本质上还是属于同一进程,所以虚拟内存(MMU,TLB),全局变量等资源是不需要替换的。
   所以系统调用导致的上下文切换代价也比进程上下文切换的代价低。

4.4、中断上下文切换

    在前面的文章[Linux是怎么从网络上接收数据包的](https://mp.weixin.qq.com/s?__biz=Mzk0NjQ5ODY5OQ==&mid=2247484261&idx=1&sn=5a4a2fa9f56990b758ea4b8d70fdd842&chksm=c3047e81f473f79700dbad1cf126aa0311b396efb05134169149efa78e703a51e00ed03d1805&token=944685945&lang=zh_CN&scene=21#wechat_redirect)中我们有详细解释过中断的概念,中断在操作系统中拥有最高优先级,当发生中断时,需要停止当前进程的远行,优先处理中断。那么这个过程就需要把进程的上下文保存,等处理完中断后再次运行该进程的时候,就可以从上次暂停的地方继续运行。  
    中断上下文切换也与进程上下文切换不同,中断执行程序通常也是操作系统内核的一部分,并不涉及到进程的用户态。所以中断上下文切换也不需要保存和恢复这个进程的虚拟内存、全局变量等用户态资源。只需要切换CPU寄存器,程序计数器等资源。

五、怎么观察上下文切换次数

    通过上面的描述,我们知道了上下文切换涉及到寄存器,程序计数器,虚拟内存等资源的保存和恢复,这些操作必然是需要时间的。如果程序耗费在这些地方的时间变多了,那么性能肯定就会变差,接下来我们就来看看如何观察上下文切换耗费的时间。  
    vmstat 是一个常用的系统性能分析工具,主要用来分析系统的内存使用情况,也常用来分析 CPU 上下文切换和中断的次数。  

vmstat输出格式如下,总体分为四部分:

procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  1      0 8693200 707820 4257088    0    0     0     7    0    0  1  1 99  0  0
 0  0      0 8692860 707820 4257156    0    0     0    60 2043 3710  1  1 98  0  0
 0  0      0 8696820 707820 4257140    0    0     0    46 2024 3688  0  0 99  0  0
system
in每秒的系统中断数,包括时钟中断。
cs每秒上下文切换的次数

这里我们主要关注in和cs。
vmstat 只给出了系统总体的上下文切换情况,要想查看每个进程的详细情况,可以使用pidstat。

pidstat -w

18:25:31      UID       PID   cswch/s nvcswch/s  Command
18:25:36        0       215      0.21      0.00  agent
18:25:36        0       275      0.84      0.21  base
18:25:36        0       456    103.35      0.00  cmlb_client
18:25:36        0       470     10.48      0.00  monitor_agent
18:25:36        0      2069      1.47      0.00  datawalker_logb
18:25:36        0   1060290      0.21      0.00  pidstat

重点关注:

  • cswch:表示每秒自愿上下文切换的次数,指的是进程无法获取所需资源,导致的上下文切换。比如说, I/O、内存等系统资源不足时,就会发生自愿上下文切换。
  • nvcswch:表示每秒非自愿上下文切换的次数。指进程由于时间片已到等原因,被系统强制调度,进而发生的上下文切换。

六、上下文切换过多的影响

这里使用github上的一段代码测试「CPU 密集型任务」在不同线程数下的耗时情况。代码地址:https://github.com/nickliuchao/threadpollsizetest
图片

  • 横坐标为线程数
  • 纵坐标为耗时,单位ms

从上图可知,当线程数量太小,同一时间大量请求将被阻塞在线程队列中排队等待执行线程,此时 CPU 没有得到充分利用;当线程数量太大,被创建的执行线程同时在争取 CPU 资源,又会导致大量的上下文切换,从而增加线程的执行时间,影响了整体执行效率。
同时并发编程网也提供了另一种测试方式:https://ifeve.com/java-context-switch/

推荐阅读

1、原来阿里字节员工简历长这样
2、一条SQL差点引发离职
3、MySQL并发插入导致死锁


如果你也觉得我的分享有价值,记得点赞或者收藏哦!你的鼓励与支持,会让我更有动力写出更好的文章哦!
更多精彩内容,请关注公众号「云舒编程」

  • 21
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值