CPU缓存一致性原理

CPU缓存一致性原理

在本站的文章CPU缓存那些事儿中, 介绍了cpu的多级缓存的架构和cpu缓存行cache line的结构。CPU对于缓存的操作包含读和写,读操作在cache line中有所涉及,在本文中,将重点讨论CPU对于缓存进行写时的行为。

单核CPU对高速缓存的读操作

CPU对于高速缓存的读操作的过程在之前的文章中有提到过,这里梳理一下其流程:

  • 1.首先对于一个内存地址,CPU会按照索引规则(直接映射/多路组相连/全相连)优先去cache line中进行检索。
  • 2.如果检索到了,意味着该内存地址的内容已经存在于cache中,则直接读取内容到CPU中,流程结束。如果没有检索到,进行步骤3。
  • 3.此时确认内存的数据不在cache line中,如果cache已经存满或者已经被其他内存地址映射,则进入步骤4,如果cache line中还有空位,则进入步骤5.
  • 4.执行缓存淘汰策略,腾出位置
  • 5.加载内存数据到cache line中,将cache块上的内容读取到cpu中。

对于单核CPU, 对cache的读操作的流程如下所示:

single-cpu-read

单核CPU对于高速缓存的写操作

CPU对于高速缓存的写操作比读操作要复杂一些,写操作会修改cache中的数据,而这一数据最终需要同步到内存中,在同步到内存这个点上,写操作的策略可以分为写直达策略写回策略。注意这里的讨论仍然是针对单核CPU而言的。

写直达策略(Write-Through)

写直达策略的思想是对于写入操作,同时修改cache中的数据内存中的数据,使得cache和内存中的数据保持一致。这将使得cache和内存拥有数据的强一致性

写直达策略中写数据的流程可以被细化为下列的步骤:

  • 1.判断待写入的数据是否已经存在于cache中。如写入的数据已经存在于cache中,则跳转到步骤2,否则跳转到步骤3。
  • 2.将数据写入cache中。
  • 3.将数据写入内存。

而写直达策略中读数据的流程并没有任何改变,因为cache和内存的数据是强一致的。

写直达策略的流程概括为如下所示:

single-cpu-read

对于写直达策略,读cache的流程并没有任何改变,这一点和写回策略是有区别的。

写直达的优缺点如下:

优点:对于写操作而言,可以保证内存和高速缓存内容的强一致性

缺点:由于每次写入操作都需要将数据写入内存,使得写操作的耗时增加,失去了高速缓存的高效性

写回策略(Write-Back)

写直达策略的思想是当需要修改cache时,延迟修改内存。在每个cache line上增加了Dirty的标记,当修改了cache中的内容时,会将Dirty标记置位,表明它的数据和内存数据不一致。注意此时,并不会立即写回内存。只有当cache块被替换出去时,才会回写到内存中。这其实是一种异步的思想,其相较于写直达也要复杂一些。写回策略实现的是一种最终一致性

写回策略是由读和写配合完成的。

对于写回策略中的写操作,其流程如下:

  • 1.首先判断写操作的地址是否存在于cache中,如存在,进入步骤6,否则进入步骤2。
  • 2.如果写操作的地址不在cache中,则尝试读取内存数据到cache中。这里需要判断cache此时是否已经满或者被占用。如果已经满或者被占用,则进入步骤3,否则进入步骤5。
  • 3.如果cache此时是否已经满或者被占用,则使用替换策略挪出空位。此时还需要判断被替换出的cache line是否为脏数据,如果是脏数据,则进入步骤4,否则进入步骤5
  • 4.将被替换出的cache line的数据写回内存中。
  • 5.读取内存块的数据到cache line中。
  • 6.将数据写入cache中
  • 7.将cache line标记为脏数据。

由于替换策略也可能发生在读操作中,因此对于写回策略中的读操作也发生了修改,其流程如下所示:

  • 1.首先对于一个内存地址,CPU会按照索引规则(直接映射/多路组相连/全相连)优先去cache line中进行检索。
  • 2.如果检索到了,意味着该内存地址的内容已经存在于cache中,则直接读取内容到CPU中,流程结束。如果没有检索到,进行步骤3。
  • 3.此时确认内存的数据不在cache line中,如果cache已经存满或者已经被其他内存地址映射,则进入步骤4,如果cache line中还有空位,则进入步骤6.
  • 4.执行缓存淘汰策略,腾出位置,此时需要判断被替换出的cache line是否为脏数据,如果是,则进入步骤5,否则进入步骤6.
  • 5.将脏数据写回到内存中。
  • 6.加载内存数据到cache line中,将cache块上的内容读取到cpu中。

写回策略的读写操作的流程如下所示:

single_cpu_write_back

多CPU核的缓存一致性问题。

上面的写直达策略写回策略可以解决单核CPU的cache和内存的一致性问题。而现代的CPU往往都是多核CPU,每个核心都拥有其自己的cache。那么对于写操作而言,除了保证本CPU的cache和内存的一致性,还需要保证其它CPU的cache和内存的一致性问题。

MESI协议就是用来解决这个问题的。

MESI协议是一种用于保证缓存一致性的协议,其对应了CPU cache的四种状态,每个字母代表了一种状态,其含义如下:

  • M(Modified,已修改): 表明 Cache 块被修改过,但未同步回内存;
  • E(Exclusive,独占): 表明 Cache 块被当前核心独占,而其它核心的同一个 Cache 块会失效;
  • S(Shared,共享): 表明 Cache 块被多个核心持有且都是有效的;
  • I(Invalidated,已失效): 表明 Cache 块的数据是过时的。

MESI 协议其实是 CPU Cache 的有限状态机,其四种状态的转化如下图所示:

cache line

对于MESI协议,有一个可视化的网站可以演示其过程,网址:https://www.scss.tcd.ie/Jeremy.Jones/VivioJS/caches/MESIHelp.htm。

下面我们借助该网站来理解MESI每个状态的变换过程。

独占状态E转已修改状态M E->M

如下图所示,此时cpu0上有a0的缓存,a0的缓存状态是独占状态E。

EtoM_1

此时cpu0执行write a0操作,由于其它cpu上没有a0数据,状态E转为状态M:

EtoM_2

已修改状态M转共享状态S,M->S

如下图所示,此时cpu0上有a0的缓存,a0的缓存状态是已修改状态M:

MtoS_1

随后cpu1上执行read a0的操作,则cpu 0上的a0从状态M转为状态S:

MtoS_2

共享状态S转已失效状态I, S->I

如下图所示,此时cpu0上有a0的缓存,其状态为S。除此以外,cpu1上也有a0的缓存。

StoI_1

此时cpu1上执行write a0的操作,则cpu0上的a0状态从S转为了I:

StoI_2

共享状态S转独占状态E, S->E

如下图所示,此时cpu0上有a0的缓存,其状态为S。除此以外,cpu1上也有a0的缓存。

StoE_1

此时cpu1上执行write a0的操作,则cpu1上的a0状态从S转为了E:

StoE_2

已失效状态I转独占状态E,I->E

状态I转状态E有两种路径,第一种是通过Processor Read, 此时a0数据仅存在于cpu0的cache中:

ItoE_1

cpu0执行read a0操作,a0状态从I转为了E:

ItoE_2

第二种是通过process write, 注意此时a0数据存在于cpu0和cpu1的cache中:

ItoE_3

cpu0执行write a0操作,a0状态从I转为了E:

ItoE_4

已失效状态I转共享状态S,I->S

此时cpu0上a0的状态是I, 而cpu1上a0的状态是E:

ItoS_1

此时cpu0执行read a0操作,cpu0上的a0从I转为S:

ItoS_2

独占状态E转已失效状态I,E->I

此时cpu0上的有a0的缓存,且状态为E,其它cpu上没有a0的缓存:

EtoI_1

此时cpu1执行write a0的操作,此时cpu0上a0的状态从E转为I:

EtoI_2

已修改状态E转共享状态S,E->S

如下图所示,此时cpu0的独占a0:

cache line

此时cpu1执行read a0的操作:

cache line

独占状态M转失效状态I, M->I

此时cpu0上拥有a0的缓存,且状态为M,其它cpu上没有a0的缓存:

cache line

此时cpu1执行write a0的操作,则cpu0上a0缓存的状态从独占(M)转为了失效(I):

cache line

已失效状态I转已修改状态M,I->M

此时cpu0上a0缓存的状态是I,其它cpu上没有a0的缓存:

ItoM_1

此时cpu0执行write a0, 则cpu0上的a0从I转为了E:

ItoM_2

写缓冲区和失效队列

写缓冲区和失效队列实际上是对MESI的优化策略。

写缓冲区(Store Buffer)

从上面的对于MESI的理解中,不难发现,MESI协议其实并不高效。例如当CPU1将要修改cache line时,需要广播RFO获得独占权,当收到其它cpu核的ACK之前,CPU1只能空等。 这对于CPU1而言,是一种资源的浪费。写缓冲区就是为了解决这个问题的。当CPU核需要写操作时,会将写操作放入缓冲区中,然后就可以执行其它指令了。当收到其它核心的ACK后,就可以将写入的内容写入cache中。

失效队列(Invalidation Queue)

写缓冲区是对写操作发送命令时的优化,而失效队列则是针对收写操作命令时的优化。

对于其它的CPU核心而言,在其收到RFO请求时,需要更新当前CPU的cache line状态,并回复ACK。然而在收到RFO请求时,CPU核心可能在处理其它的事情,不能及时回复

写缓冲区和失效队列将RFO请求的收发修改为了异步的,这实际上实现的是一种最终一致性。这也会引入新的问题,即CPU对于指令会有重排。如果有一些程序对于内存序有要求,那么就需要进行考虑。

考虑下面的场景,cpu0和cpu1分别执行下面的指令:

cpu0上指令

a = 1  //A1
x = b  //A2

cpu1上指令

b = 1 //B1
y = a //B2

MESI-issue

由于store-buffer的存在,尽管A1操作先于A2操作之前发生,但是A1操作完成时间可能晚于A2,如下表中反应的顺序:

顺序cpu0cpu1
0a=1写入store bufferb = 1写入store buffer
1x=b
2y = a
3b = 1操作完成
4a=1操作完成

尽管A1操作发生于B2之前,但是由于写操作的异步特性,当执行到y=a时,读取到的还是旧值。同理x=b也可能读取到旧的值。

对于这种cpu层面的指令重排问题,则需要内存屏障进行解决。

总结

  • 对于单核cpu,写操作需要考虑cache和内存的一致性,通常有写直达和写回两种策略。
  • 对于多核cpu,除了cache和内存的一致性需要保证,还需要保证每个cpu的cache的数据的一致性。
  • MESI协议就是一种解决多核cpu缓存一致性的算法
  • 为了改善MESI的效率,引入了store-buffer和Invalidation Queue,提升了效率,但是也引入了指令重排的问题。通常可以使用内存屏障解决。

参考文献

https://www.scss.tcd.ie/Jeremy.Jones/VivioJS/caches/MESIHelp.htm
https://blog.csdn.net/weixin_46215617/article/details/115433851?share_token=0637ba7e-fc4b-4d21-8d6b-000301e7fe3e
https://juejin.cn/post/7158395475362578462
https://blog.51cto.com/qmiller/5285102

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/82861.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

美国大模型风向速报(一)为何重视提示工程?LangChain+向量数据库+开源大模型真香...

多家,且独家来自美国的信源同时向“亲爱的数据”表示, 提示工程(Prompt Engineering)在美国大模型领域备受重视。 读者都要聊, 那就干活。 (一)开源真香 现阶段,AI开源极客大展身手&…

【视觉SLAM入门】5.2. 2D-3D PNP 3D-3D ICP BA非线性优化方法 数学方法SVD DLT

"养气之学,戒之躁急" 1. 3D-2D PNP1.1 代数法1.1.1 DLT(直接线性变换法)1.1.2. P3P 1.2 优化法BA (Bundle Adjustment)法 2. 3D-3D ICP2.1 代数法2.1.1 SVD方法 2.2 优化(BA)法2.2.2 非线性优化方法 前置事项: 1. 3D-2D PNP 该问题描述为&am…

线性代数的学习和整理7:各种特殊矩阵(草稿-----未完成)

目录 1 单位矩阵 为什么单位矩阵I是 [1,0;0,1]T 而不是[1,1;1,1]T 2 旋转矩阵 3 伸缩矩阵 放大缩小倍数矩阵 4 镜像矩阵 5 剪切矩阵 矩阵 行向量 列向量 方阵 1 单位矩阵 [ 1 0 0 1] 为什么单位矩阵I是 [1,0;0,1]T 而不是[1,1;1,1]T 因为 矩阵 [1,0;0,1] 代表…

websocket + stomp + sockjs学习

文章目录 学习链接后台代码引入依赖application.ymlWebSocketConfigPrivateControllerWebSocketService WebSocketEventListenerCorsFilter 前端代码Room.vue 学习链接 WebSocket入门教程示例代码,代码地址已fork至本地gitee,原github代码地址&#xff…

马哈鱼数据血缘工具背后的项目: gsp_demo_java 项目简单介绍与使用

0.背景 马哈鱼数据血缘工具(https://www.sqlflow.cn/)是SQLflow工具的中文译名,实际就是sqlflow. 对于SQL flow来说,底层调用的是General SQL Parser(GSP https://sqlparser.com) 的库. 这个gsp有开源的java demo项目:https://github.com/sqlparser/gsp_demo_java 1.快速使用…

【C# 基础精讲】LINQ 基础

LINQ(Language Integrated Query)是一项强大的C#语言特性,它使数据查询和操作变得更加简洁、灵活和可读性强。通过使用LINQ,您可以使用类似SQL的语法来查询各种数据源,如集合、数组、数据库等。本文将介绍LINQ的基础概…

(排序) 剑指 Offer 45. 把数组排成最小的数 ——【Leetcode每日一题】

❓ 剑指 Offer 45. 把数组排成最小的数 难度:中等 输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。 示例 1: 输入: [10,2] 输出: “102” 示例 2: 输入: [3,30,34,5,9] 输出: “3033459”…

PV3D: A 3D GENERATIVE MODEL FOR PORTRAITVIDEO GENERATION 【2023 ICLR】

ICLR:International Conference on Learning Representations CCF-A 国际表征学习大会:深度学习的顶级会议 生成对抗网络(GANs)的最新进展已经证明了生成令人惊叹的逼真肖像图像的能力。虽然之前的一些工作已经将这种图像gan应用于无条件的2D人像视频生…

[K8s]问题描述:k8s拉起来的容器少了cuda的so文件

问题解决:需要设置Runtimes:nvidia的同时设置Default Runtimenvidia

Java请求Http接口-OkHttp(超详细-附带工具类)

简介:OkHttp是一个默认有效的HTTP客户端,有效地执行HTTP可以加快您的负载并节省带宽,如果您的服务有多个IP地址,如果第一次连接失败,OkHttp将尝试备用地址。这对于IPv4 IPv6和冗余数据中心中托管的服务是必需的。OkHt…

Win11游戏高性能模式怎么开

1、点击桌面任务栏上的“开始”图标,在打开的应用中,点击“设置”; 2、“设置”窗口,左侧找到“游戏”选项,在右侧的选项中,找到并点击打开“游戏模式”; 3、打开的“游戏模式”中,找…

搭载KaihongOS的工业平板、机器人、无人机等产品通过3.2版本兼容性测评,持续繁荣OpenHarmony生态

近日,搭载深圳开鸿数字产业发展有限公司(简称“深开鸿”)KaihongOS软件发行版的工业平板、机器人、无人机等商用产品均通过OpenAtom OpenHarmony(以下简称“OpenHarmony”)3.2 Release版本兼容性测评,获颁O…

【Vue】yarn 安装包时权限不足或者文件夹被占用导致安装失败

在一个 Vue3 项目中,用 yarn 安装 Vue 插件或者 Vue-Router 时,出现同样的 error ,如下: An unexpected error occurred: “EPERM: operation not permitted, unlink ‘C:\Codefield\项目\yupao-frontend\node_modules\esbuild\w…

c#设计模式-结构型模式 之 代理模式

前言 由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接 引用目标对象,代理对象作为访问对象和目标对象之间的中介。在学习代理模式的时候,可以去了解一下Aop切面编程AOP切面编程_aop编程…

python高级基础

文章目录 python高级基础闭包修饰器单例模式跟工厂模式工厂模式单例模式 多线程多进程创建websocket服务端手写客户端 python高级基础 闭包 简单解释一下闭包就是可以在内部访问外部函数的变量,因为如果声明全局变量,那在后面就有可能会修改 在闭包中的…

下载安装并使用小乌龟TortoiseGit

1、下载TortoiseGit安装包 官网:Download – TortoiseGit – Windows Shell Interface to Githttps://tortoisegit.org/download/ 2、小乌龟汉化包 在官网的下面就有官方提供的下载包 3、安装

android 的Thread类

Thread类 位于java.lang包下的Thread类是非常重要的线程类,它实现了Runnable接口,学习Thread类包括这些相关知识:线程的几种状态、上下文切换,Thread类中的方法的具体使用。 线程:比进程更小的执行单元,每…

消息中间件-kafka实战-第六章-kafka加线程池多线程消费

目录 参考架构图延时队列 参考 头条面试:当线上Kafka集群有大量消息积压时,如何利用多线程消费解决消费积压问题 架构图 延时队列

vulnhub靶机DarkHole_2

靶机下载地址:DarkHole: 2 ~ VulnHub 靶机发现 arp-scan -l 扫描端口 nmap --min-rate 10000 -p- 192.168.21.145 扫描服务 nmap -sV -sT -O -p22,80 192.168.21.145 漏洞扫描 nmap --scriptvuln -p22,80 192.168.21.145 这里有git源码泄露 git clone mirrors…

网络编程基础(1)

目录 网络编程解决是跨主机的进程间通讯 1、网络 2、互联网 3、ip地址 (1)ipv4: (2)ipV6:1 (3)IP地址的组成: (4)Linux查看IP地址:ifconfig 4、mac地址 5、ping Ip地址 6…