加入收藏 | 设为首页 | 会员中心 | 我要投稿 云计算网_泰州站长网 (http://www.0523zz.com/)- 视觉智能、AI应用、CDN、行业物联网、智能数字人!
当前位置: 首页 > 综合聚焦 > 创业热点 > 经验 > 正文

高性能服务端系列:处理器篇

发布时间:2016-04-14 23:31:32 所属栏目:经验 来源:阿里百川专区的网站
导读:从JMM说起,作为一名JAVA开发,特别在多线程编程实践中,了解和熟悉JAVA内存模型是很有必要的。刚开始接触内存模型的时候,有很多概念非常陌生,比如happens-before,可见

保证数据的可见性 我们知道,内存中的数据除了在内存中的副本,还有可能在各个核的CPU中,当某个核修改了对应cache中的数据后,这时其它核中对应内存地址的数据还有主存中的数据就不是最新的了,其它核为了能够读取到最新的数据,需要执行memory barrier指令,把store buffer中的修改写到主存中。

防止指令之间的重排序 前面讲到一条指令的执行会分为几个步骤,也就是pipeline,为了得到更高的性能,编译器或者处理器有可能会改变指令的执行顺序,以此来提高指令执行的并行度。不管是编译器还是处理器的重排序,都要遵守as-if-serial语义。as-if-serial说的是,不管怎么重排序,在单线程中执行这些指令,其结果应该是一样的。在多线程的情况下,需要memory barrier来保证整体的顺序,否则会出现意想不到的结果。

不同的处理器架构的memory barrier也不太一样,以Intel x86为例,有三种memory barrier

store barrier

对应sfence指令

1. 保证了sfence前后store指令的顺序,防止重排序。

2. 通过刷新store buffer保证了sfence之后的store指令全局可见之前,sfence之前的store要指令要先全局可见。

load barrier

对应lfence指令,

1. 保证了lfence前后的load指令的顺序,防止重排序。

2. 刷新load buffer。

full barrier

对应mfence指令

1. 保证了mfence前后的store和load指令的顺序,防止重排序。

2. 保证了mfence之后的store指令全局可见之前,mfence之前的store指令要先全局可见。

以java中的volatile为例,volatile的语义有几点:

1. volatile的操作是原子的

2. volatile的操作是全局可见的

3. 在一定程度上防止重排序

一般是通过插入内存屏障或者具有屏障功能的其它指令(如lock指令)来保证上面的第二和第三点。

总结

内存屏障本身非常复杂,不同的处理器的实现也很不一样,编译期间和运行期间都有内存屏障,上面是以X86为例,做了简单的介绍。但是不管是哪个平台,他们都是解决两个问题,一个是指令的排序,另一个是全局可见性。

false sharing

前面我讲到,内存中的数据是以cache line为单位从内存中读到CPU cache中的,比如有两个变量X,Y,在内存中他们俩非常近,那么很有可能在读X的时候,Y也被放到了相同cache line中。假设Thread1需要不停的写X,比如在一个循环中,而Thread2需要不停的写Y,那么在Thread1写X的时候,需要Invalid其他cache中对应的cache line,Thread2写Y的时候也要做同样的事情,这样就会不停的碰到上面说过的MESI协议的两个比较耗时的操作:

1. 当cache line状态为Invalid时,需要写入数据。

2. 把cache line的状态变为invalid

会严重影响性能。

高性能服务端系列:处理器篇

解决false sharing也比较简单,做padding就可以了。下面这段代码是disruptor中Sequence的一段代码,一般cache line是64 byte,long类型的value加上7个long做padding,正好是64byte,这样当以Sequence[]的方式使用时,不同下标的Sequence对象就会落在不同的cache line中。

高性能服务端系列:处理器篇

context switch

我们知道CPU的核数量是有限的,一般是1-32核不等,而现代操作系统是多任务操作系统,同一时刻在运行的进程数量一般都会远远超过CPU的核数量。简单的说就是并行运行的任务数量最多就是CPU的核数,但是并发运行的任务数量可以有很多。打个比方,对于单核的CPU,如果不能并发运行多个任务的话,那么所有任务都会是串行的,假设某个任务进行一次远程调用,而远程调用的时间比较长,那么这样的系统效率将会非常低,如果能并发执行的话,在一个任务等待的时候,操作系统可以把CPU时间片分给其它其它任务运行,而前一个任务等待完毕后,操作系统再次调度CPU,重新让它继续运行,这样对于使用者来说,感觉就像是同时在运行多个任务。

context switch的开销

保存和恢复context

那是不是并发运行的任务越多越好呢?答案当然是否定的,并发运行任务带来的最大的缺点就是上下文切换(context switch)带来的开销。上下文(context)指的是当前任务运行时,在CPU寄存器,程序计数器中保存的状态。每一次进行线程切换时,需要保存当前所有寄存器、程序计数器、栈指针等,等线程切换回来的时候又要对这些内容进行恢复。

污染CPU缓存

当频繁的进行线程切换的时候,因为运行的任务不一样了,对应的CPU cache中的数据也不一样,当被阻塞的线程重新执行的时候,CPU cache中的内容很可能已经发生了变化,以前在缓存中的数据可能要重新从主存中加载。

因此,在系统设计的时候,应该尽量避免不必要的上下文切换。比如nodejs、golang、actor model、netty等等这些并发模型,都减少了不必要的上下文切换。

关于阿里百川

阿里百川(baichuan.taobao.com)是阿里巴巴集团“云”+“端”的核心战略是阿里巴巴集团无线开放平台,基于世界级的后端服务和成熟的商业组件,通过“技术、商业及大数据”的开放,为移动创业者提供可快速搭建App、商业化APP并提升用户体验的解决方案;同时提供多元化的创业服务-物理空间、孵化运营、创业投资等,为移动创业者提供全面保障。

注:相关网站建设技巧阅读请移步到建站教程频道。

(编辑:云计算网_泰州站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

推荐文章
    热点阅读