[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)

大家都在看

  • ThreadLocalRandom类原理分析

    public int nextInt(int bound) { if (bound > 31); else { for (int u = r; u – (r = u % bo…

    Java 2023年6月5日
    082
  • SSH 端口转发(本地转发与远程转发)

    SSH 端口转发处于机构SSH用于从所述客户端机器隧道应用程序的端口到服务器计算机上,或反之亦然。它可用于为 遗留应用程序添加加密、 通过防火墙,并且一些系统管理员和 IT 专业人…

    Java 2023年5月30日
    088
  • 数据结构与算法之希尔排序

    希尔排序则是通过添加一个步长的概念,每次把当前元素与增加步长后的元素比较,如果交换则交换.然后再次增加步长去比较,这个过程与插入排序一样.希尔排序与插入排序的 区别在于希尔排序通过…

    Java 2023年6月8日
    091
  • 【动力节点Springboot学习笔记】Springboot集成redis

    今天这篇博客也是学习springboot做的学习笔记,关于springboot集成redis,分享给有需要的小伙伴们,视频看的动力节点 动力节点王鹤老师讲解的springboot教…

    Java 2023年6月7日
    083
  • [游戏引擎中文版]YU-RIS 4.5 最新中文支持版

    今天给大家带来一个很重要的引擎——YU-RIS 相对于复杂的krkr2,YU-RIS要可爱的多。 为什么我要说她很重要呢? 我看过不下10篇,日本GAL引擎 介绍 基本每一篇都会介…

    Java 2023年5月29日
    089
  • phpshe xml注入

    php商城系统 xml注入 页面样式 Xml原理参考: https://www.cnblogs.com/20175211lyz/p/11413335.html 漏洞函数simple…

    Java 2023年6月6日
    082
  • 关于 .NET 与 JAVA 在 JIT 编译上的一些差异

    最近因为公司的一些原因,我也开始学习一些 JAVA 的知识。虽然我一直是以 .NET 语言为主的程序员,但是我并不排斥任何其它语言。在此并不讨论 JAVA .NET 的好坏,仅仅是…

    Java 2023年5月29日
    095
  • 常量以及数值问题

    常量定义 在 Java 语言中,主要是利用关键字 final 来定义一个常量。常量一旦被初始化后不能 再更改其值。 常量声明格式为: final type varName = va…

    Java 2023年6月7日
    075
  • 上传图像

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

    Java 2023年6月6日
    099
  • MySQL基础入门(1)

    为什么学习MySQL 关系数据库管理系统(Relational Database Management System, RDBMS)是一种极为重要的工具,其应用十分广泛,从商业、科…

    Java 2023年6月5日
    074
  • 面试突击57:聚簇索引=主键索引吗?

    在 InnoDB 引擎中,每张表都会有一个特殊的索引”聚簇索引”,也被称之为聚集索引,它是用来存储行数据的。 一般情况下,聚簇索引等同于主键索引,但这里有一…

    Java 2023年5月29日
    085
  • rabbitmq 一些参数设置

    rabbitmq 方法的一些参数设置以及解释 消费者采用手动应答 // 采用手动应答 false 采用手动应答,true采用自动应答 boolean autoAck = false…

    Java 2023年6月9日
    076
  • 01-Spring Security框架学习–入门(二)

    一、入门案例 Spring Security 自定义登录界面 通过之前的一节 01-Spring Security框架学习–入门(一)的简单演示,Spring secu…

    Java 2023年6月10日
    082
  • spring @Value注入map、List、Bean、static变量方式及详细使用

    https://blog.csdn.net/ywb201314/article/details/118093873?utm_medium=distribute.pc_relevan…

    Java 2023年5月30日
    086
  • Servlet 4

    Servlet 4.0规约简介 Servlet 4.0 规约是JCP组织定义的Web规约,JSR编号369。 1.1 什么是Servlet? Servlet 是基于Java技术的W…

    Java 2023年6月15日
    073
  • Effective Java 第三版——83. 明智谨慎地使用延迟初始化

    Tips书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code注意,书中的有些代码里方法是基于Java 9…

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