操作系统内功篇:硬件结构之CPU缓存一致性

一 CPU Cache的数据写入

1.1 CPU Cache的结构

是由很多个Cache Line组成的,CPU Line是CPU从内存读取的基本单位,CPU Line是由多个标志+数据块组成。

1.2 CPU Cache数据的写入

数据不仅仅只有读取,还有数据的写入,写入数据也是先将数据写入到CPU 的L1Cache中,然后再由 CPU 控制器将数据写入到内存。怎么知道数据是否要写入到内存,很简单,内存中和Cache相对应的内容不同时,就i该把Cache的数据同步到内存。

什么时机把Cache中的数据写回到内存?

1)写直达

2)写回

1.2.1 写直达

写直达: 最简单的保持Cache和内存一致性的方法,就是在数据写入到Cache时候,同时也写入到内存一份。这个方法,写入到L1Cache之前先判断是否已经在Cache中了,如果在Cache里面,先把Cache内的数据更新,并把数据也写到内存。 如果Cache没有该数据,就直接把该数据写入到内存,不用写入到Cache了。 缺点:无论数据在不在L1Cache,都会写入到内存,其实会花费很多时间,降低效率。

1.2.2 写回

写回: 新写入的数据只会被写入到Cache Block中,只有当修改过的Cache Block被替换时候才写到内存中,减少了数据写回到内存的频率。

具体做法:

发生写操作时,数据就已经在CPU Cache中的话,则把数据更新到CPU Cache中,同时标记CPU Cache中的这个Cache Line为脏,代表内存和CPU Cache中的数据不一致,这种情况不用将数据写入到内存。

如果当发生写操作时,数据所对应的 Cache Block 里存放的是「别的内存地址的数据」的话,就要检查这个 Cache Line 里的数据有没有被标记为脏的,如果是脏的话,我们就要把这个 Cache Line 里的数据写回到内存,然后再把当前要写入的数据,写入到这个 Cache Line 里,同时也把它标记为脏的;如果 Cache Line 里面的数据没有被标记为脏,则就直接将数据写入到这个 Cache Line里,然后再把这个 Cache Line 标记为脏的就好了。 可以发现写回这个方法,在把数据写入到 Cache 的时候,只有在缓存不命中,同时数据对应的 Cache 中的 Cache Line 为脏标记的情况下,才会将数据写到内存中,而在缓存命中的情况下,则在写入后 Cache 后,只需把该数据对应的 Cache Line 标记为脏即可,而不用写到内存里。 这样的好处是,如果我们大量的操作都能够命中缓存,那么大部分时间里 CPU 都不需要读写内存,自然性能相比写直达会高很多。

二 缓存一致性问题

多核心CPU是造成缓存一致性问题的主要原因。

以一个二核心CPU为例,假设 A 号核心和 B 号核心同时运行两个线程,都操作共同的变量 i(初始值为 0 )。这时如果 A 号核心执行了 i++ 语句的时候,为了考虑性能,使用了我们前面所说的写回策略,先把值为 1 的执行结果写入到 L1/L2 Cache 中,然后把 L1/L2 Cache 中对应的 Block 标记为脏的,这个时候数据其实没有被同步到内存中的,因为写回策略,只有在 A 号核心中的这个 Cache Block 要被替换的时候,数据才会写入到内存里。 如果这时旁边的 B 核心尝试从内存读取 i 变量的值,则读到的将会是错误的值,因为刚才 A 核心更新 i 值还没写入到内存中,内存中的值还依然是 0。这个就是所谓的缓存一致性问题,A 核心和 B 核心的缓存,在这个时候是不一致,从而会导致错误。

那么,要解决这一问题,就需要一种机制,来同步两个不同核心里面的缓存数据。要实现的这个机制的话,要保证做到下面这两点:

●第一点,某个 CPU 核心里的 Cache 数据更新时,必须要传播到其他核心的 Cache,这个称为写传播;

●第二点,某个 CPU 核心里对数据的操作顺序,必须在其他核心看起来顺序是一样的,这个称为事务的串形化。

第一点写传播很容易就理解,当某个核心在 Cache 更新了数据,就需要同步到其他核心的 Cache 里。而对于第二点事务事的串形化,我们举个例子来理解它:

假设我们有一个含有 4 个核心的 CPU,这 4 个核心都操作共同的变量 i(初始值为 0 )。A 号核心先把 i 值变为 100,而此时同一时间,B 号核心先把 i 值变为 200,这里两个修改,都会「传播」到 C 和 D 号核心。那么问题就来了,C 号核心先收到了 A 号核心更新数据的事件,再收到 B 号核心更新数据的事件,因此 C 号核心看到的变量 i 是先变成 100,后变成 200。 而如果 D 号核心收到的事件是反过来的,则 D 号核心看到的是变量 i 先变成 200,再变成 100,虽然是做到了写传播,但是各个 Cache 里面的数据还是不一致的。 所以,我们要保证 C 号核心和 D 号核心都能看到相同顺序的数据变化,比如变量 i 都是先变成 100,再变成 200,这样的过程就是事务的串形化。 要实现事务串形化,要做到两点:

●CPU 核心对于 Cache 中数据的操作,需要同步给其他 CPU 核心。

●要引入「锁」的概念,如果两个 CPU 核心里有相同数据的 Cache,那么对于这个 Cache 数据的更新,只有拿到了「锁」,才能进行对应的数据更新。

三 总线嗅探

写传播的原则就是当某个 CPU 核心更新了 Cache 中的数据,要把该事件广播通知到其他核心。最常见实现的方式是总线嗅探。 还是以前面的 i 变量例子来说明总线嗅探的工作机制:

当 A 号 CPU 核心修改了 L1 Cache 中 i 变量的值,A核心通过总线把这个事件广播通知给其他所有的核心,然后每个CPU 核心都会监听总线上的广播事件,并检查是否有相同的数据在自己的 L1 Cache 里面,如果 B 号 CPU 核心的 L1 Cache 中有该数据,那么也需要把该数据更新到自己的 L1 Cache。

总线嗅探方法很简单,不过 CPU 需要每时每刻监听总线上的一切活动,但是不管别的核心的 Cache 是否缓存相同的数据,都需要发出一个广播事件,这无疑会加重总线的负载。 另外,总线嗅探只是保证了某个 CPU 核心的 Cache 更新数据这个事件能被其他 CPU于是,有一个协议基于总线嗅探机制实现了事务串形化,用状态机机制降低了总线带宽压力,这个协议就是 MESI 协议,这个协议就做到了 CPU 缓存一致性。

四 MESI协议

MESI 协议其实是 4 个状态单词的开头字母写,分别是:

●Modified,已修改

●Exclusive,独占

●Shared,共享

●Invalidated,已失效

这四个状态来标记 Cache Line 四个不同的状态。

●「已修改」状态就是我们前面提到的脏标记,代表该 Cache Block 上的数据已经被更新过,但是还没有写到内存里。而「已失效」状态,表示的是这个 Cache Block 里的数据已经失效了,不可以读取该状态的数据。

●「独占」和「共享」状态都代表 Cache Block 里的数据是干净的,也就是说,这个时候 Cache Block 里的数据和内存里面的数据是一致性的。

●「独占」和「共享」的差别在于,独占状态的时候,数据只存储在一个 CPU 核心的 Cache 里,而其他 CPU 核心的 Cache 没有该数据。这个时候,如果要向独占的 Cache 写数据,就可以直接自由地写入,而不需要广播给其他 CPU 核心,因为只有你这有这个数据,就不存在缓存一致性的问题了,于是就可以随便操作该数据。

●在「独占」状态下的数据,如果有其他核心从内存读取了相同的数据到各自的 Cache ,那么这个时候,独占状态下的数据就会变成共享状态。

●「共享」状态代表着相同的数据在多个 CPU 核心的 Cache 里都有,所以当我们要更新 Cache 里面的数据的时候,不能直接修改,而是要先向所有的其他 CPU 核心广播一个请求,要求先把其他核心的 Cache 中对应的 Cache Line 标记为「无效」状态,然后再更新当前 Cache 里面的数据。

4.1 举例

举例说明: 我们举个具体的例子来看看这四个状态的转换:

1.当 A 号 CPU 核心从内存读取变量 i 的值,数据被缓存在 A 号 CPU 核心自己的 Cache 里面,此时其他 CPU 核心的 Cache 没有缓存该数据,于是标记 Cache Line 状态为「独占」,此时其 Cache 中的数据与内存是一致的; 1.然后 B 号 CPU 核心也从内存读取了变量 i 的值,此时会发送消息给其他 CPU 核心,由于 A 号 CPU 核心已经缓存了该数据,所以会把数据返回给 B 号 CPU 核心。在这个时候, A 和 B 核心缓存了相同的数据,Cache Line 的状态就会变成「共享」,并且其 Cache 中的数据与内存也是一致的; 1.当 A 号 CPU 核心要修改 Cache 中 i 变量的值,发现数据对应的 Cache Line 的状态是共享状态,则要向所有的其他 CPU 核心广播一个请求,要求先把其他核心的 Cache 中对应的 Cache Line 标记为「无效」状态,然后 A 号 CPU 核心才更新 Cache 里面的数据,同时标记 Cache Line 为「已修改」状态,此时 Cache 中的数据就与内存不一致了。 1.如果 A 号 CPU 核心「继续」修改 Cache 中 i 变量的值,由于此时的 Cache Line 是「已修改」状态,因此不需要给其他 CPU 核心发送消息,直接更新数据即可。 1.如果 A 号 CPU 核心的 Cache 里的 i 变量对应的 Cache Line 要被「替换」,发现Cache Line 状态是「已修改」状态,就会在替换前先把数据同步到内存。 1.所以,可以发现当 Cache Line 状态是「已修改」或者「独占」状态时,修改更新其数据不需要发送广播给其他 CPU 核心,这在一定程度上减少了总线带宽压力。

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

【智能算法】斑鬣狗优化算法(SHO)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过。 3.代码实现4.参考文献 1.背景 2017年,Dhiman等人受到斑鬣狗自然狩猎行为启发,提出了斑鬣狗优化算法(Spotted Hyena Optimizer, SHO)。 2.算法原理 2.1算法思想 SHO将斑鬣狗狩猎行为分为围捕-狩猎-进攻三…

JVM实战篇

内存调优 内存溢出和内存泄漏 内存泄漏:在java中如果不再使用一个对象,但是该对象依然在GC ROOT的引用链上,这个对象就不会被垃圾回收器回收。 内存泄漏绝大多数情况都是由堆内存泄漏引起的,所以后续没有特别说明则讨论的都是堆…

matplotlib画堆叠、并列直方图

在用 matplotlib.pyplot.hist 画分布图时,若总分布由几个分量组成(如高斯混合),想用不同颜色标识出来,方便看到各分量占比,参考 [1]。 效果: 分布由两个分量(x、y)组成…

二,几何相交---4,BO算法---(4)可能的三种情况

从上到下,扫描线经过有三种情况: 第一种情况,加入线段e的左端点,那么原来的状态pred->suc变成pred->e->suc 第二种情况,经过线段e的右端点,状态pred->e->suc,变成pred->suc&a…

Java 面试题之框架

1. Spring 是什么 Sping 是包含了众多工具方法的 IOC 容器,IOC是控制反转,说的是对象的创建和销毁的权利都交给 Spring 来管理了, 它本身又具备了存储对象和获取对象的能力. 。 容器:字面意思,用来容纳某种物品的装置。 比如 L…

JavaScript练手小技巧:数字反转时钟

样式基于博主的这篇文章: CSS3技巧38:3D 翻转数字效果-CSDN博客 既然可以实现翻转数字了,肯定就可以跟 JS 相结合去完成一些数字展示效果。 比如,数字反转时钟。 为了方便,所有 HTML 数字根据时间动态生成。因此&a…

企业内部培训考试系统培训计划功能说明

培训计划是预设好的一套课程系列,包含课程和考试,分多个阶段,每完成一个阶段就会在学习地图上留下标记,让用户看到自己的努力成果,增强成就感,从而坚持完成课程。 企业内部培训考试系统中如何设置培训计划…

一起玩儿3D打印机——06 Marlin固件的配置(三)

摘要:本文介绍Marlin固件的配置方法 25. 启用EEPROM参数保存功能 #define EEPROM_SETTINGS 打开此功能,会将部分参数保存在打印机中,这样通过屏幕就可以进行调节,而无需重刷固件。 26. 启用板载SD卡支持 #define SDSUPPORT 如…

【QT+QGIS跨平台编译】之七十七:【QGIS_Gui跨平台编译】—【错误处理:字符串错误】

文章目录 一、字符串错误二、处理方法三、涉及到的文件一、字符串错误 常量中有换行符错误:(也有const char * 到 LPCWSTR 转换的错误) 二、处理方法 需要把对应的文档用记事本打开,另存为 “带有BOM的UTF-8” 三、涉及到的文件 src\gui\qgsadvanceddigitizingdockwidge…

WebServer -- 架构图 面试题(上)

目录 🎂前言 🌼流程图 && 架构图 1)什么是 WebServer 2)服务器基本框架 3)Reactor && Proactor 模式 4)同步 I/O 模拟Proactor模式(Linux) 5)主从…

C语言基础练习——Day10

目录 选择题 编程题 不用加减乘除做加法 找到所有数组中消失的数字 选择题 1、求函数返回值,传入-1,则在64位机器上函数返回 int func(int x) {int count 0;while (x){count;x x&(x - 1);//与运算}return count; } A 死循环B 64C 32D 16 答案&…

别急,先了解一下什么是REST API吧

1、先想一想Rest API的用途和场景 Rest API的常用场景:前后端分离,前端可多样化,还有与其他系统集成:RESTful API 可以与其他系统进行集成,例如第三方登录、支付和社交媒体平台等。 现在我们知道了如何使用 servlet …

redis 常见的异常

目录 一、缓存穿透 1、概念 解决方案 (1)布隆过滤器 (2)、缓存空对象 二、缓存雪崩 1、概念 解决方案 (1)redis高可用 (2)限流降级 (3)数据预热 一、缓存穿透 1、概念 缓…

仰卧起坐计数,YOLOV8POSE

仰卧起坐计数,YOLOV8POSE 通过计算膝盖、腰部、肩部的夹角,计算仰卧起坐的次数

分数相加减(C语言)

一、流程图&#xff1b; 二、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>int main() {//初始化变量值&#xff1b;int fenmu 2;int result 1;int fuhao 1;//执行循环&#xff1b;while (fenmu < 100){//运算&#xff1b;fuhao (-1…

汽车电子与软件架构概述

汽车电子与软件架构概述 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师 (Wechat:gongkenan2013)。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 本就是小人物,输了就是输了,不要在意别人怎么看自己。江湖一碗茶,喝完再挣扎,出门靠自己…

Sentinel篇:线程隔离和熔断降级

书接上回&#xff1a;微服务&#xff1a;Sentinel篇 3. 隔离和降级 限流是一种预防措施&#xff0c;虽然限流可以尽量避免因高并发而引起的服务故障&#xff0c;但服务还会因为其它原因而故障。 而要将这些故障控制在一定范围&#xff0c;避免雪崩&#xff0c;就要靠线程隔离…

MySQL数据库实现增删改查基础操作

准备工作 安装mysql8.0 (安装时一定要记住用户名和密码)安装数据库可视化视图工具Navicat 请注意⚠️⚠️⚠️⚠️ a. 编程类所有软件不要安装在中文目录下 b. Navicat破解版下载安装教程&#xff1a;&#xff08;由于文章审核提示版权问题&#xff0c;链接不方便给出&#xff…

Docker 哲学 - 容器操作

容器&#xff1a; 创建 停止 删除 强制删除&#xff08;正在运行&#xff09; run stop rm rm -f 列出本地容器&#xff1a; docker ps / docker container ls 镜像&#xff1a; search pull run &#xff1a; …

Orbit 使用指南 03 | 与刚体交互 | Isaac Sim | Omniverse

如是我闻&#xff1a; “在之前的指南中&#xff0c;我们讨论了独立脚本&#xff08; standalone script&#xff09;的基本工作原理以及如何在模拟器中生成不同的对象&#xff08;prims&#xff09;。在指南03中&#xff0c;我们将展示如何创建并与刚体进行交互。为此&#xf…