[Java多线程编程之四] CPU缓存和内存屏障

一、CPU三级缓存

1、缓存的作用

  CPU的结构很复杂,简单地说由运算器和寄存器组成。程序运行时,需要CPU去执行运算,运算是由运算器来执行,运算器可以做加减乘除运算以及与或非逻辑运算,运算过程中可能需要临时存放数据到某个地方,寄存器就起到这个作用。



  虽然寄存器可以存储一些运行时数据,但是容量是很小的,程序运行时产生的大部分数据(比如Java对象)是存储在内存中的,并且程序指令也是存储在内存中,所以程序运行时CPU需要频繁操作内存,包括读取和写入,但是CPU的速度太快了,如果直接操作内存,CPU的大部分时间会处于等待内存操作的空转状态,内存完全跟不上节奏,怎么办?



  这时候就需要有缓存的存在了,内存将CPU要读取的数据源源不断地加载到缓存中,CPU读取缓存,缓存的速度比内存快多了,勉强能跟得上CPU大哥的节奏了!

  但是CPU表示缓存你还是太慢了,我带不动,所以产生了一级缓存、二级缓存、三级缓存,一级缓存最快、二级次之、三级最慢;缓存容量则反过来,一级最小,二级大一些,三级最大。
  为什么缓存能加快系统运行?举个例子,现在需要很多水,如果直接打开水龙头,要放很久,如果有水桶已经放满了水,取水是不是会快点?如果需要更多的水,我们弄个水塔,平时储满水,假如水桶的水不够用,则打开水塔,这样就达到快速取水的目的。
  缓存可以看成是一个数据的池子,由于速度越快的缓存单位存储空间的价格也越高,所以要有多级缓存,速度快的存储小,速度慢的存储大,多级缓存结合达到总体上经济又实惠的效果,在三级缓存中,每一级缓存都有80%左右的命中率,如果本级缓存中找不到CPU要的数据,则进入下一级缓存中查找,三级缓存中找不到则进入内存查找,这种可能性只有0.8%,大多数情况下可以保证了CPU快速运行,避免内存延迟。


CPU读取数据顺序
  • L1 Cahce(一级缓存)是CPU第一层高速缓存,分为数据缓存和指令缓存,一般服务器CPU的L1缓存的容量通常在32-4096KB。
  • L2 是由于L1高速缓存容量的限制,为了再次提高CPU的运算速度,在CPU外部放置一高速存储器,即二级缓存。
  • L3 缓存的应用可以进一步降低内存延迟,同时提升大数据量计算时处理器的性能;具有较大L3缓存的处理器可以提供更有效的文件系统缓存行为及较短信息和处理器队列长度;现在的计算机都内置了L3,并且多核计算机中多个CPU可以共享一个L3缓存,但是每个CPU都会有它自己的L1、L2。


    CPU缓存设计示意图

  CPU在读取数据时,先在L1中寻找,再从L2寻找,再从L3寻找,然后是内存,最后是外存储器。

2、缓存同步协议

  对于多核计算机,多个CPU可能会读取同样的数据进行缓存,在经过不同运算之后,最终写入主内存,那么问题来了,写入的时候谁先谁后,最终写入主内存中的数据以哪个CPU为准?
  为了应对这种高速缓存回写的场景,众多CPU厂商联合制定了缓存一致性协议MESI协议,并分别实现,MESI协议规定每条缓存有个状态位,同时定义了下面四个状态:

  • 修改态(Modified)- 此cache行已被修改过(脏行),内容已不同于主存,为此cache专有;
  • 专有态(Exclusive)- 此cache行内容同于主存,但不出现于其他cache中;
  • 共享态(Shared)- 此cache行内容同于主存,但也出现于其他cache中;
  • 无效态(Invalid)- 此cache行内容无效(空行)。

  当计算机中有多个处理器时,单个CPU对缓存中数据进行了改动,需要通知给其他CPU;这意味着CPU不仅要控制自己的读写操作,还要监听其他CPU发出的通知,从而保证最终一致,所以在MESI协议下不存在 “可见性” 问题。


二、CPU缓存模型

  缓存一致性协议可以保证CPU缓存一致,但是对性能有很大的消耗,因此CPU的架构师在计算单元和L1之前又增加了 Store Buffer、Load Buffer,如下所示:


加了 Store Buffer 和 Load Buffer 的 CPU 缓存体系

  L1、L2、L3和主内存之间是同步的,这是通过缓存一致性协议来保证的,但是 Store Buffer、Load Buffer 和L1之间却是异步的。也就是说,往内存中写入一个变量,这个变量会保存在 Store Buffer 里面,稍后才异步写入L1中,同时同步写入主内存中。

  从操作系统内核的角度,缓存模型可以被简化为如下图所示:


操作系统内核视角下的 CPU 缓存模型

  多 CPU,每个 CPU 多核,每个核上面可能还有多个硬件线程,对于操作系统来讲,就相当于一个个的逻辑 CPU。每个逻辑 CPU 都有自己的缓存,这些缓存和主内存之间不是完全同步的。

  对应到 Java 里,就是 JVM 抽象内存模型,如下:

JVM抽象内存模型



三、重排序

3.1 分类

3.1.1 编译器重排序

  对没有先后依赖关系的语句,编译器可以重新调整语句的执行顺序。

3.1.2 CPU指令重排序

  在指令级别,让没有依赖关系的多条指令并行。

  运行时指令重排是CPU为了避免阻塞等待某些操作需要的资源,先去执行可执行的指令,当阻塞等待的资源获取到时,再去执行对应的指令的操作。

   代码示例:


指令重排序

  指令重排的场景:当CPU写缓存时发现缓存区块正在被其他CPU占用,为了提高CPU处理性能,可能将后面的读缓存命令优先执行。

3.1.3 CPU内存重排序

  CPU 有自己的缓存,指令的执行顺序和写入主内存的顺序不完全一致,这是造成内存可见性问题的主要原因,因为按照执行顺序,读写都是在 Load Buffer 和 Store Buffer 中完成的,缓存中的数据不会马上同步到主内存中去,因此数据同步到主内存中的顺序可能跟指令执行的顺序不一致,最终造成 “内存可见性” 问题。

3.2 as-if-serial

  不管怎么重排序(编译器和处理器为了提高并行度),单线程程序的执行结果不能被改变,编译器、runtime和处理器都必须遵循as-if-serial语义,也就是说,编译器和处理器不会对存在数据依赖关系的操作做重排序。


3.3 指令重排存在问题

  但是对多线程程序来说,指令逻辑无法分辨因果关联,因此指令重排可能会出现乱序执行,导致程序运行结果错误,因此在多线程程序中有些时候需要通类似 volatile 修饰变量之类的方式来禁止可能导致可见性问题的指令重排。


四、内存屏障

  为了禁止编译器重排序和 CPU 重排序,在编译器和 CPU 层面都有对应的指令,也就是内存屏障(Memory Barrier),这也正是 JMM 和 happen-before 规则的底层实现原理。

  处理器提供了两个内存屏障指令:

1、写内存屏障(Store Memory Barrier)

  在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见;当发生这种强制写入主内存的显式调用,CPU就不会处于性能优化考虑进行指令重排。

2、读内存屏障(Load Memory Barrier)

  在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制重新从主内存加载数据,让CPU缓存与主内存保持一致,避免缓存导致的一致性问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容