9

你能像鸟一样,边xx边oo么?CPU给我们的启示

 3 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzA4MTc4NTUxNQ%3D%3D&%3Bmid=2650521351&%3Bidx=1&%3Bsn=5247e9f68e29128cda88156237ccffe6
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

e6JBVrU.gif

原创:小姐姐味道(微信公众号ID:xjjdog),欢迎分享,转载请保留出处。

作为一只鸟,可以边吃东西边拉屎么?这对一个养过鹦鹉的人来说,答案是肯定的。

从鸟嘴到鸟大肠,整个过程是串行的。虽然有些曲折,但方向是单一的,出口是一定的。只要鸟能够克服一些心理上的不悦,它就能办得到。

这种情况在一些原始的物种身上显得有些尴尬。比如水螅,它属于腔肠动物,无性生殖。这几个单词都挺唬人,但它的体内只有一个空腔。从哪里吃,就从哪里出。

nm6r6n6.png!web

同是地球上的物种,从长远看来,我们还是它的近亲。

作为高级哺乳动物,我们能够在呼吸的时候,同时说话,也能够同时听到声音,看到赏心悦目的风景。如果你想的话,也可以办到鹦鹉做的事。

这些信息被收集之后,一股脑发送给大脑进行处理。有可能是像深度学习一样,存了一堆权重,但这些信息到底是如何处理的,我们现在还不得而知。

所以程序员们转而研究计算机,毕竟这个相比起“最后归途是哲学”的人类大脑,就像是个玩具。

我们都知道,干一堆事干不好,不如集中精力把一件事干好。这并不是说一个人没能力把所有的事情干好,而是在不同的事务之间切换,是要耗费资源的。

你的大脑好不容易熟悉了一个工作场景,结果突然调度给它另外一项任务,它就要花很长时间切换到新的工作场景中。

有时候代码写多了,我就连说话都开始口吃。但一直不停的说说说,就又恢复了。

所以,所有的人都恨零零散散的工作事务。尤其是恨哪些不断给你小事情,但又毫不相关的任务的领导。

到头来,感觉做了很多,但一点成果都没有,感觉人都废了。

不要怕,我们看看CPU是怎么处理的。

3ERNjmV.png!web

CPU处理任务时不是一直只处理一个,而是通过给每个线程分配CPU时间片,时间片用完了就切换下一个线程。

时间片非常短,一般只有几十毫秒,CPU通过不停地切换线程执行,但我们几乎感觉不到任务的停滞。因为对人类来说,高质量的游戏,每秒只需要60帧,就算是流畅了。

这个时间还是相当可观的,特别是在进程上下文切换次数较多的情况下,很容易导致CPU将大量时间消耗在寄存器,内核栈以及虚拟内存等资源的保存和恢复上,进而大大缩短了真正运行进程的时间。

对于Linux来说。程序在执行过程中通常有 用户态内核态 两种状态,CPU对处于内核态根据上下文环境进一步细分,因此有了下面三种状态:

  • 内核态,运行于进程上下文,内核代表进程运行于内核空间。

  • 内核态,运行于中断上下文,内核代表硬件运行于内核空间。

  • 用户态,运行于用户空间

我们看一下Linux的top命令,是怎么显示内核态和用户态的。

36NRJza.png!web

如上图,us就是user的意思;sy就是system的意思。分别代表了用户态和内核态。

如果sy占用的太高,就有可能是上下文切换和中断太频繁了。

那什么是上下文?

所谓的上下文,说白了就是一个环境。比如你去食堂带着饭盒,去厕所带着厕纸。要是搞乱了,去厕所带着饭盒,感觉上就不正常。操作系统为每一个进程,分配了这么一个上下文,用来存放:代码、数据、用户堆栈、共享存储区、寄存器、进程控制块等。

先不要管里面的细节了,反正内容很多,切换肯定是要有陈本的。比如,厕纸放在家里卧室柜子的第三层小隔间。

2A3yqqM.png!web

vmstat命令显示的这几列,就是这么个意思。cs不是csgo的缩写,它的全拼是 context switch

在每个进程里,也可以看到累加的值。

[root@localhost ~]# cat /proc/2788/status
...
voluntary_ctxt_switches: 93950
nonvoluntary_ctxt_switches: 171204

cs如果太高,那就是线程或者进程开的太多了。

上下文切换又分为2种。

让步式上下文切换和抢占式上下文切换。

下面先说下让步式上下文切换。我们拿Java中的cas操作来看就可以了。

cas除了 compare and switch 原始指令支持以外,还需要一个循环来保证。

public final long getAndAddLong(Object var1, long var2, long var4) {
        long var6;
        do {
            var6 = this.getLongVolatile(var1, var2);
        } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
        return var6;
}

代码放在循环里,在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。所以,让步式上下文切换,是指执行线程主动释放CPU,与锁竞争严重程度成正比,可通过减少锁竞争来避免。

而抢占式上下文切换,是指线程因分配的时间片用尽而被迫放弃CPU,或者被其他优先级更高的线程所抢占。一般由于线程数大于CPU可用核心数引起,可通过调整线程数,适当减少线程数来避免。

那为啥Java的线程就能够比多进程速度快一些呢?因为Java的线程本质上也是一种轻量级进程,但它的虚拟内存等信息是共享的,只需要切换线程的私有数据,寄存器等不共享的数据。即使这样,也会耗费不少时间。

nyyaym7.png!web

使用perf命令同样能够观测到这个上下文切换到过程和数量。比如:

# 跟踪所有上下文切换,直到Ctrl-C:
perf record -e context-switches -c 1 -a

# 包括使用的原始设置(请参阅:man perf_event_open):
perf record -vv -e context-switches -a

# 使用堆栈跟踪的示例上下文切换,直到Ctrl-C:
perf record -e context-switches -ag

使用 perf report 即可查看相关结果。

E3iErmR.png!web

对于计算机来说,效率最高的依然是专心做一件事。一定程度上,你也算是计算机的老板。如果你一直让它干一些杂活,把它当牛使,那你的计算机效率不一定会高。

有些人很聪明,他一定知道这种来回切换的方式对你的工作效率影响巨大。排除他愚蠢的属性,就只剩下坏:给你一堆烂七八糟的事,搞得你身心疲惫,最后又和你讲结果导向的---一定是你的领导故意为之。

作者简介: 小姐姐味道 (xjjdog),一个不允许程序员走弯路的公众号。聚焦基础架构和Linux。十年架构,日百亿流量,与你探讨高并发世界,给你不一样的味道。我的个人微信xjjdog0,欢迎添加好友,进一步交流。

近期热门文章

传统企业的人才们,先别忙着跳“互联网”!

对2B和2C的一些思考

《Serverless,会将工程师带入“不归路”!

介绍Serverless,以及一些展望

《必看!java后端,亮剑诛仙》

后端技术索引,中肯火爆。全网转载上百次。

《学完这100多技术,能当架构师么?(非广告)》

精准点评100多框架,帮你选型

EnUneen.gif


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK