进程上下文切换 – 残酷的性能杀手(上)(转载cppthinker.com)
对于服务器的优化,很多人都有自己的经验和见解,但就我观察,有两点常常会被人忽视 – 上下文切换 和 Cache Line同步 问题,人们往往都会习惯性地把视线集中在尽力减少内存拷贝,减少IO次数这样的问题上,不可否认它们一样重要,但一个高性能服务器需要更细致地去考察这些问题,这个问题我将分成两篇文章来写:
1)从一些我们常用的用户空间函数,到linux内核代码的跟踪,来看一个上下文切换是如何产生的
2)从实际数据来看它对我们程序的影响
另外,关于Cache Line 的测试大家可移步 http://www.cppthinker.com/cpp/9/cpu_cache/
Context Switch简介 -
上下文切换(以下简称CS)的定义,http://www.linfo.org/context_switch.html 此文中已做了详细的说明,这里我又偷懒不详细解释了:) 只提炼以下几个关键要点:
*) context(这里我觉得叫process context更合适)是指CPU寄存器和程序计数器在任何时间点的内容
*)CS可以描述为kernel执行下面的操作
1. 挂起一个进程,并储存该进程当时在内存中所反映出的状态
2. 从内存中恢复下一个要执行的进程,恢复该进程原来的状态到寄存器,返回到其上次暂停的执行代码然后继续执行
*)CS只能发生在内核态(kernel mode)
*)system call会陷入内核态,是user mode => kernel mode的过程,我们称之为mode switch,但不表明会发生CS(其实mode switch同样也会做很多和CS一样的流程,例如通过寄存器传递user mode 和 kernel mode之间的一些参数)
*)一个硬件中断的产生,也可能导致kernel收到signal后进行CS
什么样的操作可能会引起CS -
首先我们一定是希望减少CS,那什么样的操作会发生CS呢?也许看了上面的介绍你还云里雾里?
首先,linux中一个进程的时间片到期,或是有更高优先级的进程抢占时,是会发生CS的,但这些都是我们应用开发者不可控的。那么我们不妨更多地从应用开发者(user space)的角度来看这个问题,我们的进程可以主动地向内核申请进行CS,而用户空间通常有两种手段能达到这一“目的”:
1)休眠当前进程/线程
2)唤醒其他进程/线程
pthread库中的pthread_cond_wait 和 pthread_cond_signal就是很好的例子(虽然是针对线程,但linux内核并不区分进程和线程,线程只是共享了address space和其他资源罢了),pthread_cond_wait负责将当前线程挂起并进入休眠,直到条件成立的那一刻,而pthread_cond_signal则是唤醒守候条件的线程。我们直接来看它们的代码吧
pthread_cond_wait.c

1 int
2 __pthread_cond_wait (cond, mutex)
3 pthread_cond_t *cond;
4 pthread_mutex_t *mutex;
5 {
6 struct _pthread_cleanup_buffer buffer;
7 struct _condvar_cleanup_buffer cbuffer;
8 int err;
9 int pshared = (cond->__data.__mutex == (void *) ~0l)
10 ? LLL_SHARED : LLL_PRIVATE;
11
12 /* yunjie: 这里省略了部分代码 */
13
14 do
15 {
16 /* yunjie: 这里省略了部分代码 */
17
18 /* Wait until woken by signal or broadcast. */
19 lll_futex_wait (&cond->__data.__futex, futex_val, pshared);
20
21 /* yunjie: 这里省略了部分代码 */
22
23 /* If a broadcast happened, we are done. */
24 if (cbuffer.bc_seq != cond->__data.__broadcast_seq)
25 goto bc_out;
26
27 /* Check whether we are eligible for wakeup. */
28 val = cond->__data.__wakeup_seq;
29 }
30 while (val == seq || cond->__data.__woken_seq == val);
31
32 /* Another thread woken up. */
33 ++cond->__data.__woken_seq;
34
35 bc_out:
36 /* yunjie: 这里省略了部分代码 */
37 return __pthread_mutex_cond_lock (mutex);
38 }

代码已经经过精简,但我们仍然直接把目光放到19行,lll_futex_wait,这是一个pthread内部宏,用处是调用系统调用sys_futex(futex是一种user mode和kernel mode混合mutex,这里不展开讲了),这个操作会将当前线程挂起休眠(马上我们将会到内核中一探究竟)
lll_futex_wait宏展开的全貌
可以看到,该宏的行为很简单,就是通过内嵌汇编的方式,快速调用syscall:SYS_futex,所以我们也不用再多费口舌,直接看kernel的实现吧
linux/kernel/futex.c
linux 2.5内核以后都使用这种SYSCALL_DEFINE的方式来实现内核对应的syscall(我这里阅读的是inux-2.6.27.62内核), 略过一些条件检测和参数拷贝的代码,我们可以看到在函数最后调用了do_futex,由于这里内核会进行多个函数地跳转,我这里就不一一贴代码污染大家了
大致流程: pthread_cond_wait => sys_futex => do_futex => futex_wait (蓝色部分为内核调用流程)
futex_wait中的部分代码
以上是futex_wait的一部分代码,主要逻辑是将当前进程/线程的状态设为TASK_INTERRUPTIBLE(可被信号打断),然后将当前进程/线程加入到内核的wait队列(等待某种条件发生而暂时不会进行抢占的进程序列),之后会调用schedule,这是内核用于调度进程的函数,在其内部还会调用context_switch,在这里就不展开,但有一点可以肯定就是当前进程/线程会休眠,然后内核会调度器他还有时间片的进程/线程来抢占CPU,这样pthread_cond_wait就完成了一次CS
pthread_cond_signal的流程基本和pthread_cond_wait一致,这里都不再贴代码耽误时间
大致流程:pthread_cond_signal => SYS_futex => do_futex => futex_wake => wake_futex => __wake_up => __wake_up_common => try_to_wake_up (蓝色部分为内核调用流程)
try_to_wake_up()会设置一个need_resched标志,该标志标明内核是否需要重新执行一次调度,当syscall返回到user space或是中断返回时,内核会检查它,如果已被设置,内核会在继续执行之前调用调度程序,之后我们万能的schedule函数就会在wait_queue(还记得吗,我们调用pthread_cond_wait的线程还在里面呢)中去拿出进程并挑选一个让其抢占CPU,所以,根据我们跟踪的内核代码,pthread_cond_signal也会发生一次CS
本篇结束 -
会造成CS的函数远远不止这些,例如我们平时遇到mutex竞争,或是我们调用sleep时,都会发生,我们总是忽略了它的存在,但它却默默地扼杀着我们的程序性能(相信我,它比你想象中要更严重),在下一篇中我将以chaos库(我编写的一个开源网络库)中的一个多线程组件为例,给大家演示CS所带来的性能下降
希望对大家有帮助 :)
本人博客原文地址 - http://www.cppthinker.com/linux/224/context_switch_1/
进程上下文切换 – 残酷的性能杀手(上)(转载cppthinker.com)的更多相关文章
- Linux按照CPU、内存、磁盘IO、网络性能监测【转载】
本文转载地址:https://my.oschina.net/chape/blog/159640 系统优化是一项复杂.繁琐.长期的工作,优化前需要监测.采集.测试.评估,优化后也需要测试.采集.评估.监 ...
- [.net 面向对象程序设计进阶] (16) 多线程(Multithreading)(一) 利用多线程提高程序性能(上)
[.net 面向对象程序设计进阶] (16) 多线程(Multithreading)(一) 利用多线程提高程序性能(上) 本节导读: 随着硬件和网络的高速发展,为多线程(Multithreading) ...
- 识别SQL Server 性能杀手
性能优化的重点在于识别定位问题,预先了解主要的性能杀手,能够更快的定位到问题并将工作集中在可能的原因之上. SQL SERVER性能杀手主要集中在如下几类: 1.1 低质量的索引 低质量的索引通常 ...
- 03讲基础篇:经常说的CPU上下文切换是什么意思(上)
小结 总结一下,不管是哪种场景导致的上下文切换,你都应该知道: CPU 上下文切换,是保证 Linux 系统正常工作的核心功能之一,一般情况下不需要我们特别关注. 但过多的上下文切换,会把CPU时间消 ...
- 【基础知识】CPU上下文切换(进程上下文切换 - 线程上下文切换 - 中断上下文切换)
CPU 上下文切换是什么 CPU 上下文切换,就是先把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器 ...
- 伪共享(false sharing),并发编程无声的性能杀手
在并发编程过程中,我们大部分的焦点都放在如何控制共享变量的访问控制上(代码层面),但是很少人会关注系统硬件及 JVM 底层相关的影响因素.前段时间学习了一个牛X的高性能异步处理框架 Disruptor ...
- 获取Linux进程运行在哪个CPU内核上面的方法
首先,当某些时候,在一段程序或者借助第三方软件进行程序协助的时候,在性能的优化,以及程序bug的排除上面,可能会想知道该程序执行的进程被调度到了哪一个CPU内核进行工作,从而可以推断是否是受限于硬件还 ...
- Linux 有问必答:如何知道进程运行在哪个 CPU 内核上?
问题:我有个 Linux 进程运行在多核处理器系统上.怎样才能找出哪个 CPU 内核正在运行该进程? 当你在 多核 NUMA 处理器上运 行需要较高性能的 HPC(高性能计算)程序或非常消耗网络资源的 ...
- python进程、线程、协程(转载)
python 线程与进程简介 进程与线程的历史 我们都知道计算机是由硬件和软件组成的.硬件中的CPU是计算机的核心,它承担计算机的所有任务. 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资 ...
随机推荐
- Sqli-labs less 11
Less-11 从这一关开始我们开始进入到post注入的世界了,什么是post呢?就是数据从客户端提交到服务器端,例如我们在登录过程中,输入用户名和密码,用户名和密码以表单的形式提交,提交到服务器后服 ...
- 【Go】基础语法之接口
接口定义: 利用关键字interface来定义一个接口,接口是一组方法的集合. 例如: type People interface { Show(name string, age int) (id i ...
- 设计模式-外观模式(Facade Pattern)
本文由@呆代待殆原创,转载请注明出处:http://www.cnblogs.com/coffeeSS/ 外观模式简介 外观模式的作用用一句话说就是简化接口,举个例子楼主每次编程的时候都要点开IDE.点 ...
- CSS 笔记——文本字体
5. 文本字体 -> 文本 (1)text-indent 基本语法 text-indent : length 语法取值 length : 百分比数字 | 由浮点数字和单位标识符组成的长度值,允许 ...
- [BZOJ2159]Crash的文明世界(斯特林数+树形DP)
题意:给定一棵树,求$S(i)=\sum_{j=1}^{n}dist(i,j)^k$.题解:根据斯特林数反演得到:$n^m=\sum_{i=0}^{n}C(n,i)\times i!\times S( ...
- CodeForces - 875D High Cry
题面在这里! 直接考虑每个位置成为最右边的最大值的位置,统计不合法区间,补集转化一下就好啦. 复杂度O(N * 30) #include<bits/stdc++.h> #define ll ...
- Spring的Bean生命周期理解
首先,在经历过很多次的面试之后,一直不能很好的叙述关于springbean的生命周期这个概念.今日对于springBean的生命周期进行一个总结. 一.springBean的生命周期: 如下图所示: ...
- php 自动补位
1 sprintf("%06d", 2); 2 str_pad();
- [Bug]Object reference not set to an instance of an object.
引言 今天在客户这儿,由一个问题导致,需求的变化,不得不修改代码,在记录日志中出现该问题. 原因 通过id查找相关信息,没有判断是否为null,集合是否有数据. Object reference no ...
- C#开源框架整理
Json.NET http://json.codeplex.com/ Json.Net 是一个读写Json效率比较高的.Net框架.Json.Net 使得在.Net环境下使用Json更加简单.通过Li ...