[JVM] CPU缓存一致性协议

CPU缓存一致性协议

CPU高速缓存

CPU缓存是位于cpu和内存之间的临时数据交换器,它的容量比内存小的夺但是交换速度要比内存快得多,主要是 为了解决cpu运行时的处理速度与内存读写速度不匹配的问题。

cpu在执行指令时需要从内存中获取所需要的指令和数据,但是cpu的处理速度远大于内存的读写速度,所以cpu如果直接从内存中读写数据需要等待一定的时间周期,这样会造成浪费且影响性能。所以引入了 cpu缓存

缓存与主存(内存)

[JVM] CPU缓存一致性协议

如图。主存(内存): DRAM,缓存: SRAM

常见的CPU缓存结构为三级缓存。 L1 (一级缓存)、 L2 (二级缓存)、 L3 (三级缓存),级别越小越接近CPU,容量越小读写速度也越快。

L1L2属于片内缓存(CPU核内), L3是CPU之间的共享缓存。

查找缓存时也是由近及远, L1 -> L2 -> L3 -> 主存

缓存行

缓存行是缓存中可以分配的最小存储单位,通常为64个字节,CPU缓存是由一组缓存行的固定大小的数据块组成的。

局部性原理

CPU将需要重复处理的数据和指令放在CPU缓存中,而不是内存中,减少了响应时间,体现了 局部性原理

  • 时间局部性:如果某个数据被访问,那它很可能在不久的将来被再次访问。
  • 空间局部性:如果某个数据被访问,那与他相邻的数据也可能很快被访问。

CPU执行计算过程

  • 程序和数据被加载到主内存
  • 指令和数据被加载到CPU高速缓存
  • CPU执行指令,把结果写回高速缓存
  • 将高速缓存中的数据写回到主内存

[JVM] CPU缓存一致性协议

CPU缓存一致性协议

引入了CPU高速缓存之后提升了效率,但是同时也会引发主存与缓存不一致的问题。

为了保证多个CPU缓存中共享数据的一致性,提出了 MESI协议,定义了缓存行( Cache Line)的四种状态。而CPU对缓存行的四种操作可能会产生不一致的状态,因此缓存控制器监听到本地操作和远程操作的时候,需要对地址一致的缓存行状态进行一致性的修改,从而保证数据在多个缓存之间保持一致性。

MESI协议

缓存行的四种状态:

状态 描述 监听任务 状态转换 M(Modified)修改 该缓存行有效,数据被修改导致与内存中数据不一致,数据只存在于当前CPU缓存中。 缓存行必须时刻监听所有试图读该缓存行相对就主存的操作,这种操作必须在缓存将该缓存行写回主存并将状态变成S(共享)状态之前被延迟执行。 当被写回主存之后,该缓存行的状态会变成独享(exclusive)状态。 E(Exclusive)独享 该缓存行有效,只有当前CPU有数据,与内存中数据一致,只存在于当前CPU缓存中。 缓存行也必须监听其它缓存读主存中该缓存行的操作,一旦有这种操作,该缓存行需要变成S(共享)状态。 当CPU修改该缓存行中内容时,该状态可以变成Modified状态 S(Shared)共享 该缓存行有效,与内存中数据一致,数据存在于很多CPU缓存中(多个CPU拥有相同数据) 缓存行也必须监听其它缓存使该缓存行无效或者独享该缓存行的请求,并将该缓存行变成无效(Invalid)。 当有一个CPU修改该缓存行时,其它CPU中该缓存行可以被作废(变成无效状态 Invalid)。 I(Invalid)无效 该缓存行无效。 – –

下图表示了当一个缓存行(Cache line)的调整的状态的时候,另外一个缓存行(Cache line)需要调整的状态。

状态 M E S
I M

× × × √
E

× × × √
S

× × √ √
I

√ √ √ √

对于M和E状态而言总是精确的,他们在和该缓存行的真正状态是一致的,而S状态可能是非一致的
如果一个缓存将处于S状态的缓存行作废了,而另一个缓存实际上可能已经独享了该缓存行,但是该缓存却不会将该缓存行升迁为E状态,这是因为其它缓存不会广播他们作废掉该缓存行的通知,同样由于缓存并没有保存该缓存行的copy的数量,因此(即使有这种通知)也没有办法确定自己是否已经独享了该缓存行。

[JVM] CPU缓存一致性协议

状态 触发本地读取 触发本地写入 触发远端读取 触发远端写入
M(修改)

本地cache:M

触发cache:M

其他cache:I 本地cache:M

触发cache:M

其他cache:I 本地cache:M→E→S 触发cache:I→S 其他cache:I→S 同步主内存后修改为E独享,同步触发、其他cache后本地、触发、其他cache修改为S共享 本地cache:M→E→S→I 触发cache:I→S→E→M 其他cache:I→S→I 同步和读取一样,同步完成后触发cache改为M,本地、其他cache改为I
E(独享)

本地cache:E 触发cache:E 其他cache:I 本地cache:E→M 触发cache:E→M 其他cache:I 本地cache变更为M,其他cache状态应当是I(无效) 本地cache:E→S 触发cache:I→S 其他cache:I→S 当其他cache要读取该数据时,其他、触发、本地cache都被设置为S(共享) 本地cache:E→S→I 触发cache:I→S→E→M 其他cache:I→S→I 当触发cache修改本地cache独享数据时时,将本地、触发、其他cache修改为S共享.然后触发cache修改为独享,其他、本地cache修改为I(无效),触发cache再修改为M
S(共享)

本地cache:S 触发cache:S 其他cache:S 本地cache:S→E→M 触发cache:S→E→M 其他cache:S→I 当本地cache修改时,将本地cache修改为E,其他cache修改为I,然后再将本地cache为M状态 本地cache:S 触发cache:S 其他cache:S 本地cache:S→I 触发cache:S→E→M 其他cache:S→I 当触发cache要修改本地共享数据时,触发cache修改为E(独享),本地、其他cache修改为I(无效),触发cache再次修改为M(修改)
I(无效)

本地cache:I→S或者I→E 触发cache:I→S或者I →E 其他cache:E、M、I→S、I 本地、触发cache将从I无效修改为S共享或者E独享,其他cache将从E、M、I 变为S或者I 本地cache:I→S→E→M 触发cache:I→S→E→M 其他cache:M、E、S→S→I 既然是本cache是I,其他cache操作与它无关 既然是本cache是I,其他cache操作与它无关

(上图和表格并不是很理解。)

多核缓存协同操作

假设有三个CPU A、B、C,对应三个缓存分别是cache a、b、c。在主内存中定义了 x的引用值为0。

[JVM] CPU缓存一致性协议

单核读取的执行流程是:

  • CPU A发出了一条指令,从主内存中读取 x
  • 从主内存通过 bus 读取到 CPU A 的缓存中(远端读取 Remote read),这时该 Cache line 修改为 E 状态(独享)。

[JVM] CPU缓存一致性协议

双核读取的执行流程是:

  • CPU A发出了一条指令,从主内存中读取 x
  • CPU A从主内存通过bus读取到 cache a 中并将该 Cache line 设置为E状态。
  • CPU B发出了一条指令,从主内存中读取 x
  • CPU B试图从主内存中读取 x时,CPU A检测到了地址冲突。这时CPU A对相关数据做出响应。此时 x存储于 cache a 和 cache b 中, x在 chche a 和 cache b 中都被设置为S状态(共享)。

[JVM] CPU缓存一致性协议

修改数据的执行流程是:

  • CPU A 计算完成后发指令需要修改 x.

  • CPU A 将 x设置为M状态(修改)并通知缓存了 x的 CPU B, CPU B 将本地 cache b 中的 x设置为 I状态(无效)

  • CPU A 对 x进行赋值。

[JVM] CPU缓存一致性协议

同步数据的执行流程是:

  • CPU B 发出了要读取x的指令。
  • CPU B 通知CPU A,CPU A将修改后的数据同步到主内存时cache a 修改为E(独享)
  • CPU A同步CPU B的x,将cache a和同步后cache b中的x设置为S状态(共享)。

[JVM] CPU缓存一致性协议

CPU存储模型

MESI协议为了保证多个 CPU cache 中共享数据的一致性,定义了 Cache line 的四种状态,而 CPU 对 cache 的 4种操作可能会产生不一致状态,因此 cache 控制器监听到本地操作和远程操作的时候,需要对地址一致的 Cache line 状态做出一定的修改,从而保证数据在多个cache之间流转的一致性。

但是,缓存的一致性消息传递是要时间的,这就使得状态切换会有更多的延迟。

某些状态的切换需要特殊的处理,可能会阻塞处理器。这些都将会导致各种各样的稳定性和性能问题。

比如你需要修改本地缓存中的一条信息,那么你必须将 I(无效)状态通知到其他拥有该缓存数据的CPU缓存中,并且等待确认。等待确认的过程会阻塞处理器,这会降低处理器的性能。因为这个等待远远比一个指令的执行时间长的多。所以,为了为了避免这种阻塞导致时间的浪费,引入了存储缓存(Store Buffer)和无效队列(Invalidate Queue)。

存储缓存

在没有存储缓存时,CPU 要写入一个量,有以下情况:

  • 量不在该 CPU 缓存中,则需要发送 Read Invalidate 信号,再等待此信号返回,之后再写入量到缓存中。
  • 量在该 CPU 缓存中,如果该量的状态是 Exclusive 则直接更改。而如果是 Shared 则需要发送 Invalidate 消息让其它 CPU 感知到这一更改后再更改。

这些情况中,很有可能会触发该 CPU 与其它 CPU 进行通讯,接着需要等待它们回复。这会浪费大量的时钟周期!为了提高效率,可以使用 异步的方式去处理:先将值写入到一个 Buffer 中,再发送通讯的信号,等到信号被响应,再应用到 cache 中。并且此 Buffer 能够接受该 CPU 读值。这个 Buffer 就是 Store Buffer。而不须要等待对某个量的赋值指令的完成才继续执行下一条指令,直接去 Store Buffer 中读该量的值,这种优化叫 Store Forwarding

无效队列

同理,解决了主动发送信号端的效率问题,那么,接受端 CPU 接受到 Invalidate 信号后如果立即采取相应行动(去其它 CPU 同步值),再返回响应信号,则时钟周期也太长了,此处也可优化。接受端 CPU 接受到信号后不是立即采取行动,而是将 Invalidate 信号插入到一个队列 Queue 中,立即作出响应。等到合适的时机,再去处理这个 Queue 中的 Invalidate 信号,并作相应处理。这个 Queue 就是 Invalidate Queue

乱序执行

乱序执行( out-of-orderexecution:是指CPU允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理的技术。这样将根据各电路单元的状态和各指令能否提前执行的具体情况分析后,将能提前执行的指令立即发送给相应电路。

这好比请A、B、C三个名人为晚会题写横幅”春节联欢晚会”六个大字,每人各写两个字。如果这时在一张大纸上按顺序由A写好”春节”后再交给B写”联欢”,然后再由C写”晚会”,那么这样在A写的时候,B和C必须等待,而在B写的时候C仍然要等待而A已经没事了。

但如果采用三个人分别用三张纸同时写的做法, 那么B和C都不必须等待就可以同时各写各的了,甚至C和B还可以比A先写好也没关系(就象乱序执行),但当他们都写完后就必须重新在横幅上(自然可以由别人做,就象CPU中乱序执行后的重新排列单元)按”春节联欢晚会”的顺序排好才能挂出去。

所以,CPU 为什么会有乱序执行优化?本质原因是 CPU为了效率,将长费时的操作”异步”执行,排在后面的指令不等前面的指令执行完毕就开始执行后面的指令。而且允许排在前面的长费时指令后于排在后面的指令执行完。

CPU 执行乱序主要有以下几种:

  • 写写乱序(store store)a=1;b=2; -> b=2;a=1;
  • 写读乱序(store load)a=1;load(b); -> load(b);a=1;
  • 读读乱序(load load)load(a);load(b); -> load(b);load(a);
  • 读写乱序(load store)load(a);b=2; -> b=2;load(a);

总而言之, CPU的乱序执行优化指的是处理器为提高运算速度而做出违背代码原有顺序的优化

Original: https://www.cnblogs.com/knqiufan/p/16177257.html
Author: knqiufan
Title: [JVM] CPU缓存一致性协议

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/570461/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Java源码赏析(六)Java String 三顾

    在大致了解了String之后,可能有的读者发现了,我们并没有谈到CharSequence接口。 原因是在这一节,CharSequence要和StringBuilder(Java1….

    Java 2023年6月8日
    071
  • java 锁 种类

    公平锁/非公平锁 可重入锁 独享锁/共享锁 互斥锁/读写锁 乐观锁/悲观锁 分段锁 偏向锁/轻量级锁/重量级锁 自旋锁 在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这…

    Java 2023年5月29日
    086
  • Mybatis配置解析(核心配置文件)

    4、配置解析 4.1、核心配置文件 Mybatis的配置文件包含了会深深影响mybatis行为的设置和属性信息 mybatis-config.xml properties(属性)重…

    Java 2023年6月13日
    0133
  • mysql时间日期

    1.日期(date)函数返回当前日期:CURDATE(), CURRENT_DATE(), CURRENT_DATE sql;gutter:true; select CURDATE…

    Java 2023年6月15日
    067
  • Java 变量作用域

    变量作用域,即变量可被访问的范围 Java中变量分为 全局变量、 局&…

    Java 2023年6月5日
    0153
  • Elasticsearch 入门实战(7)–Data Stream

    数据量 (Data Stream) 是在 Elasticsearch 7.9 版推出的一项功能,它可以很方便的处理时间序列数据。 1、简介 1.1、什么是 Time Series …

    Java 2023年6月16日
    083
  • Netty源码分析之ByteBuf(一)—ByteBuf中API及类型概述

    ByteBuf是Netty中主要的数据容器与操作工具,也是Netty内存管理优化的具体实现,本章我们先从整体上对ByteBuf进行一个概述; AbstractByteBuf是整个B…

    Java 2023年6月9日
    086
  • NoteOfMySQL-07-索引

    1. 索引概述 创建索引的目的是为了优化数据库的查询速度,不添加索引的情况下需要遍历所有数据才能进行删、查、改等操作。 2. 索引存储类型 存储类型 支持的存储引擎 B型树(BTR…

    Java 2023年6月5日
    0123
  • 入门clash 及 proxy 坑点

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Java 2023年6月7日
    091
  • JAVA生成appId和appKey简单方式

    appid和appkey生成没有固定逻辑,一般根据自己的需求 生成的appid是20位,appkey是32位,代码中md5的salt可以换成自己的 以上只是一个简单方式,没啥业务逻…

    Java 2023年5月29日
    0111
  • 泛型的类型擦除后,fastjson反序列化时如何还原?

    原创:微信公众号 码农参上,欢迎分享,转载请保留出处。 哈喽大家好啊,我是Hydra~ 在前…

    Java 2023年6月5日
    0101
  • Java8新特性之Stream–collect方法

    collect方法收集(collect)collect,收集,可以说是内容最繁多、功能最丰富的部分了。从字面上去理解,就是把一个流收集起来,最终可以是收集成一个值也可以收集成一个新…

    Java 2023年5月29日
    0106
  • Java循环中的do…while循环控制

    do…while循环格式 初始化语句 ; do { 循环体语句 ; 条件控制语句 ; } while( 条件判断语句 ); int i = 0; boolean loo…

    Java 2023年6月8日
    078
  • Mybatis动态数据源

    业务场景 现有股票与基金业务,不同的业务分在不同的库中,但有些业务类似可以基于同一套代码,例如组织架构、权限控制与客户管理,但是为区分业务线,要将数据拆分在不同的数据库中 达成效果…

    Java 2023年6月8日
    097
  • 题目:模拟网站的登录,客户端录入账号密码,然后服务器端进行验证(TCP)

    题目:模拟网站的登录,客户端录入账号密码,然后服务器端进行验证(TCP) 封装的类 package com.gao.Project.Pro4; import java.io.Ser…

    Java 2023年6月5日
    0104
  • 分布式锁实现方案最全解读

    前言 对多线程有所了解的朋友一般都会熟悉一个概念:锁。 在多线程并发场景下,要保证在同一时刻只有一个线程可以操作某个业务、数据或者变量,通常需要使用加锁机制。比如 synchron…

    Java 2023年6月7日
    090
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球