当前位置: 首页 > news >正文

CPU cache基本原理

CPU cache基本原理

  • 存储器层次结构
  • 存储器层次结构中的缓存
  • 高速缓存存储器
    • 直接映射高速缓存
    • 组相联高速缓存
    • 全相联高速缓存
  • 多核 CPU 下缓存问题
    • 内存的读写操作流程
    • 数据一致性与并发控制

高速缓存(cache)是一个小而快速地存储设备,它作为存储在更大,也更慢的设备中的数据对象的缓冲区域。

存储器层次结构

存储技术作为一项复杂的技术,针对于不同的存储技术,它的访问时间差异很大,速度较快的技术每字节的成本要比速度较慢的技术高,而且容量较小,CPU 与主存之间的速度差距在增大,所以为了了解决 CPU 与内存之间的速度不匹配问题,就出现了高速缓存,也就是我们平时所说的 cache

现代计算机系统当中都会依赖存储器层次结构,如下图所示,就展示了一个典型的存储器结构层次,一般而言,从高层往低层次走,存储设备变得更慢,更便宜和更大,最高层(L0)是少量快速地 CPU 寄存器,CPU 可以再一个时钟周期访问它们,接下来就是一个或者是多个小型或者是中型的基于 SRAM 的高速缓存存储器,可以再几个 CPU 时钟周期访问它们,然后是一个更大的基于 DRAM 的主存,可以再几十个或者是几百个 CPU 时钟周期访问它们,最后,有些系统包含了一层附加的远程服务器上附加的磁盘,要通过网络来访问他们。
在这里插入图片描述

存储器层次结构中的缓存

存储器结构的中心思想就是,对于每个 k ,位于 k 层的更快更小的存储设备作为位于 k + 1 层的更大更慢的存储设备的缓存,也就是说,层次结构每一层都还缓存着来自低一层的数据对象,如下图所示,第 k + 1 层存储器被划分为连续的数据对象组块(block),每一个块都有一个唯一的地址或名字区分于其他的块。

k 层的存储器被划分为较少块的集合,每个块的大小与 k + 1 层块的大小一致,也就是说第 k 层的缓存包含第 k + 1 层块的一个子集的副本。同时,数据总是以块的大小为传递单元在第 k 层到 k + 1 层之间来回进行复制的,我们可以理解为相邻的两个结构层次之间的块的大小是一致的,但是其他层次之间又可以是不同的,比如 L0L1 之间是以一个字节块为大小,L1L1 之间又可以是以几十个字节为块的大小进行传递。
在这里插入图片描述

缓存命中

当程序需要第 k + 1 的某个数据对象 d 时,它首先会在当前存储的第 k 层的一个块中查找 d ,如果 d 刚好缓存在第 k 层中,这就是我们所说的缓存命中

缓存不命中

与之相反,如果第 k 层没有缓存数据对象 d ,这就称之为缓存不命中。

当缓存不命中发生以后,第 k 层的缓存从第 k + 1 层的缓存中取出包含 d 的那个块,如果第 k 层的缓存已经满了,可能就会覆盖现存的一个块,这个覆盖的过程我们称之为替换或者是驱逐,被驱逐的这个块可以称之为牺牲块(victim block),决定怎么去替换哪个块是有缓存的替换策略来进行控制的。

高速缓存存储器

在一个计算机系统当中,其中每个每个存储器的地址有 m 位,形成 M = 2 m 2^{m} 2m 个不同的地址。如下图所示,这样的机器被组织成为一个有 S = 2 s 2^{s} 2s 个高速缓存组(cache set)的数组,每个组包含 E 个高速缓存行(cache line),每行是由一个 B = 2 b 2^{b} 2b 字节的数据块组成的。有效位(valid bit)指明这个行是否包含有意义的信息,还有 t=m-(b+s)个标记位,唯一的标识存储在这个高速缓存行中的块。

我们可以简单理解一下,当一条加载指令指示 CPU 从主存地址 A 中读取一个字时,是如何进行工作的:
参数 S 和 B 将 m 个地址分成了3个字段,A 中 s 个组索引位是一个到 S 个组的数组的索引,如下图所示,第一个组时组0,第二个是组1,以此类推,组索引告诉我们这个字必须存储在哪个组当中,当我们知道这个字必须存放在哪个组当中以后,A 中的 t 个标记位就告诉我们这个组的哪一行包含这个字(如果存在的话)。当且仅当设置了有效位并且该行的标记位与地址 A 中标记位匹配时,组中的这一行才包含这个字,一旦定位完成以后,b 个块偏移就会给出在 B 个字节的数据块中的字偏移。
在这里插入图片描述
根据每个组的高速缓存行数 E ,高速缓存被分为不同的类,主要为直接映射高速缓存、组相联高速缓存和全相联高速缓存,接下会简单的一 一介绍:

直接映射高速缓存

每个组只有一行(E = 1)的高速缓存称为直接映射高速缓存,这种也是最容易理解和实现的,如下图所示:
在这里插入图片描述
我们假设当前存在这样一个系统,它有一个 CPU,一个寄存器文件,一个L1高速缓存,一个主存,当 CPU 执行一条读取内存字 w 指令,他会向 L1 高速缓存请求这个字,如果 L1 高速缓存存在 w 字的一个副本,那么就会得到 L1 高速缓向命中,高速缓存就会很快抽取出这个 w ,返回给 CPU 。否则就是我们的缓存不命中, L1 高速缓存会向主存请求 w 这个字段,在这期间 CPU 就会进行等待,当请求的块最终从内存到达时,L1 高速缓存就会将这个块存到一个高速缓存行当中,然后从被存储的块中抽取出字 w ,最终返还给 CPU ,高速缓存确定一个请求是否命中,在抽取出对应我们所需要的字的过程分为三步:组选择->行匹配->字抽取

直接映射高速缓存的组选择

在这里插入图片描述
当前步骤,高速缓存会从 w 的地址中间抽取 s 个组索引位,这些位被解释成为一个对应组号的无符号整数,也就是说,上图中的0 0 0 1被解释成为一个选择组 1 的整数索引,这也就完成了对应的组选择这一步流程。

直接映射高速缓存的行匹配

当我们选择了组以后,接下来就需要确认是否有 w 字的副本存在于当前组的高速缓存行中,在直接映射高速缓存中很容易,因为就只有一行,所以只需保证当且仅当设置了有效位,而且高速缓存行中的标记位跟地址中的标记位匹配时,就证明这一行中包含 w 的副本。
在这里插入图片描述

直接映射高速缓存的字选择

一旦缓存命中,我们就知道 w 就在这个块中的某个地方,最后一步就是确定这个所需要的字是从块中的哪
一块位置开始的,块偏移提供的所需字的第一个字节的偏移,就如上图所示一样,把高速缓存看成一个行的数组一样。

组相联高速缓存

直接映射高速缓存冲突不命中造成的问题就是源于每个组只有一行这个限制,对于组相联高速缓存来说,就放松了这个限制,每个组保存了多余一个的高速缓存行,一个 1 < E < C / B 的高速缓存通常称为 E 路组相联高速缓存,如下图所示:
在这里插入图片描述

组相联高速缓存的组选择

组相联高速缓存中的组选择其实跟直接映射高速缓存中的组选择一样,都是以组索引位标识组,如下图所示:
在这里插入图片描述

组相联高速缓存的行匹配和字选择

组相联高速缓存的行匹配相对于直接高速缓存的行匹配就会复杂许多了,这在于他会检查多个行的标记位和有效位,以确定请求的字是否会出现在某个组当中,我们可以理解相联存储器就是一个(key,value)对的数组,以 key 为输入,返回与输入的 key 相匹配的 value 值。我们可以把组相联高速缓存中的每个组都看做是一个小的相联存储器, key 是标记和有效位,而 value 就是块的内容。
在这里插入图片描述

全相联高速缓存

全相联高速缓存是由一个包含所有高速缓存行的组(即 E = C / B)组成的如下图所示:
在这里插入图片描述

全相联高速缓存的组选择

因为默认就一个组,所以地址中就没有组索引,只被划分为一个标记和一个块偏移。
在这里插入图片描述

全相联高速缓存的行匹配和字选择

全相联高速缓存的行匹配和字选择跟组相联高速缓存是一致的,他们之间的区别就在于规模的大小。
在这里插入图片描述

通过上述所讲知识,我们对高速缓存有了一个系统的了解,缓存出现的原因就是由于硬盘的内部数据传输速度和外界介面传输速度不同,缓存在其中起到一个缓冲的作用。缓存的大小与速度是直接关系到硬盘的传输速度的重要因素,能够大幅度地提高硬盘整体性能。当硬盘存取零碎数据时需要不断地在硬盘与内存之间交换数据,如果有大缓存,则可以将那些零碎数据暂存在缓存中,减小外系统的负荷,也提高了数据的传输速度。

而缓存主要作用可以总结为以下三点:

  • 预读取:当硬盘受到CPU指令控制开始读取数据时,硬盘上的控制芯片会控制磁头把正在读取的簇的下一个或者几个簇中的数据读到缓存中(由于硬盘上数据存储时是比较连续的,所以读取命中率较高),当需要读取下一个或者几个簇中的数据的时候,硬盘则不需要再次读取数据,直接把缓存中的数据传输到内存中就可以了,由于缓存的速度远远高于磁头读写的速度,所以能够达到明显改善性能的目的。
  • 对写入动作进行缓存:当硬盘接到写入数据的指令之后,并不会马上将数据写入到盘片上,而是先暂时存储在缓存里,然后发送一个“数据已写入”的信号给系统,这时系统就会认为数据已经写入,并继续执行下面的工作,而硬盘则在空闲(不进行读取或写入的时候)时再将缓存中的数据写入到盘片上。虽然对于写入数据的性能有一定提升,但也不可避免地带来了安全隐患——如果数据还在缓存里的时候突然掉电,那么这些数据就会丢失。如果突然掉电,磁头会借助惯性将缓存中的数据写入零磁道以外的暂存区域,等到下次启动时再将这些数据写入目的地。
  • 临时存储最近访问过的数据:硬盘内部的缓存会将读取比较频繁的一些数据存储在缓存中,再次读取时就可以直接从缓存中直接传输。

多核 CPU 下缓存问题

内存的读写操作流程

  • 读操作:当CPU需要读取数据时,它会通过地址总线发送出内存地址,然后通过数据总线接收内存返回的数据。
  • 写操作:当CPU需要写数据时,它会通过地址总线发送出内存地址,并在数据总线上发送要写入的数据。

CPU 与内存的关系如下图所示:
在这里插入图片描述
实际上,高速缓存既保存数据,也保存指令。只保存指令的高速缓存称为i-cache,只保存程序数据的高速缓存称为d-cache,既保存指令又保存数据的高速缓存称之为统一的高速缓存。就如上图所示,每个核有自己私有的 L1 i-cache、L1 d-cache 和 L2 统一高速缓存,所有核共享 L3 统一的高速缓存。

CPU如何读取磁盘中的一个数据 ?

  1. 请求阶段:当程序需要数据时,CPU 发出指令要求读取磁盘数据,这个请求被送到 I/O 控制器;
  2. 寻址阶段:I/O 控制器根据 CPU 提供的地址信息,控制磁盘的机械臂移动读 / 写磁头到相应磁道,并等待目标扇区旋转到读写位置;
  3. 数据传输阶段:利用 DMA(直接存储器访问)技术,磁盘将数据直接传输至内存的缓冲区中;
  4. 完成通知:数据传输完成后,I/O 控制器向 CPU 发送中断信号,告知操作已完成;
  5. 数据处理:CPU 收到中断信号后,将数据从内存缓冲区拷贝到 CPU 缓存,再从缓存中读取数据到寄存器进行处理。

数据一致性与并发控制

对于多核心 CPU,每个核心都有自己的缓存。当不同核心同时访问和修改同一内存变量时,可能会出现缓存不一致的情况。例如,一个核心更新了其缓存中的变量值,但其他核心的缓存中仍保留着旧值。为了解决这个问题,需要采用缓存一致性协议,确保当一个核心更新了缓存中的数据时,其他核心能够及时得知并更新自己的缓存,以保证数据的一致性。

当CPU 对 Cache进行写操作时,为了确保 Cache 和内存之间的数据一致性以及提高系统性能,会采用不同的数据写入策略,其中最常见的是直写(Write Through)和写回(Write Back)策略 。

直写

直写,也被称为写直通或写穿透 。其操作逻辑非常直观:当 CPU 要写入数据时,数据会同时被写入 Cache 和内存 。也就是说,只要有写操作发生,Cache 和内存中的数据都会立即被更新 。这就好比你在两个笔记本上同时记录同一件事情,一个笔记本是 Cache,另一个笔记本是内存 。

写回

写回策略的工作机制与直写策略有很大不同 。在写回策略中,当 CPU 进行写操作时,数据首先被写入 Cache,并不会立即写入内存 。只有当被修改的 Cache Line(缓存行,是 Cache 与内存之间数据交换的最小单位)要被替换出 Cache 时(比如 Cache 已满,需要腾出空间来存放新的数据),才会将其写回到内存中 。

为了实现这种延迟写入的机制,每个 Cache Line 都有一个脏标记位(Dirty Bit) 。当数据被写入 Cache 时,脏标记位会被设置,表示该 Cache Line 中的数据已经被修改,与内存中的数据不一致 。当 Cache Line 需要被替换时,系统会检查其脏标记位 。如果脏标记位被设置,说明该 Cache Line 中的数据是最新的,需要先将其写回到内存中,然后再进行替换操作;如果脏标记位未被设置,说明该 Cache Line 中的数据与内存中的数据一致,直接进行替换即可 。

缓存一致性问题的产生

在多核 CPU 的时代,每个 CPU 核心都拥有自己独立的缓存,这虽然进一步提高了数据访问的速度,但也带来了一个新的棘手问题 —— 缓存一致性问题 。

想象一下,有一个多核心的 CPU,其中核心 A 和核心 B 都需要访问内存中的同一个数据 X 。一开始,数据 X 被加载到核心 A 和核心 B 各自的缓存中 。当核心 A 对缓存中的数据 X 进行修改时,此时核心 A 缓存中的数据 X 已经更新,但核心 B 缓存中的数据 X 仍然是旧的 。如果在这个时候,核心 B 读取自己缓存中的数据 X,就会得到一个错误的、过时的值,这就导致了数据不一致的情况 。

以一个简单的银行转账程序为例,假设有两个线程分别在不同的 CPU 核心上执行转账操作 。这两个线程都需要读取账户余额,然后进行相应的加减操作 。如果存在缓存一致性问题,就可能出现这样的情况:第一个线程读取了账户余额为 1000 元,然后在自己的缓存中进行了减 100 元的操作,但还没有将更新后的数据写回内存 。此时,第二个线程从自己的缓存中读取账户余额,由于其缓存中的数据没有更新,仍然是 1000 元,然后也进行了减 100 元的操作 。最后,两个线程都将各自缓存中的数据写回内存,结果账户余额就变成了 800 元,而实际上应该是 900 元 。这种数据不一致的情况会对程序的正确性产生严重影响,尤其是在涉及到金融、数据库等对数据准确性要求极高的领域 。

缓存一致性解决方案

要解决缓存一致性问题,首先要解决的是多个 CPU 核心之间的数据传播问题。最常见的一种解决方案呢,叫作总线嗅探——总线嗅探是一种解决缓存一致性问题的基本机制 。在这种机制下,每个CPU核心都通过总线与内存相连,并且每个核心都可以 “嗅探” 总线上的通信 。当一个CPU核心对自己缓存中的数据进行写操作时,它会向总线发送一个写请求,这个请求包含了被修改数据的地址等信息 。

总线上的其他 CPU 核心会监听这个请求,当它们发现请求中的地址与自己缓存中某数据的地址相同时,就会根据请求的类型(例如是写操作还是使缓存失效的操作),对自己缓存中的数据进行相应的处理 。如果是写操作,其他核心可能会选择将自己缓存中的该数据标记为无效,这样下次访问时就会从内存中重新读取最新的数据;如果是使缓存失效的操作,直接将对应缓存数据标记为无效即可 。

总线嗅探的优点是实现相对简单,不需要复杂的目录结构来记录缓存状态 。它能够快速地检测到其他核心对共享数据的修改,从而及时更新自己的缓存,保证数据的一致性 。然而,它也存在一些明显的缺点 。随着 CPU 核心数量的增加,总线上的通信量会大幅增加,因为每次写操作都要通过总线广播通知其他核心,这会导致总线带宽成为瓶颈,降低系统的整体性能 。而且,总线嗅探机制在处理复杂的多处理器系统时,可能会出现一些一致性问题,例如在多个核心同时进行读写操作时,可能会出现数据更新顺序不一致的情况 。

并发控制手段

锁机制是一种极为常见的并发控制手段,主要涵盖以下几种类型:

  • 互斥锁(Mutex):用于保护共享资源在同一时刻只能被一个线程访问,适用于需要对共享资源进行独占访问的场景。
  • 读写锁(Read - Write Lock):允许多个线程同时对共享资源进行读操作,但只允许一个线程进行写操作,适用于读操作频繁、写操作较少的场景。
  • 自旋锁(Spinlock):当线程尝试获取锁时,如果锁已被其他线程占用,该线程会一直循环等待,直到获取到锁为止,适用于保护临界区较小且期望临界区锁定时间较短的场景。
  • 递归锁(Reentrant Lock):允许同一个线程多次获取同一个锁,而不会导致死锁,适用于需要在同一线程中多次获取锁的场景,如递归函数调用。
  • 条件变量(Condition Variable):一种线程间通信的机制,通常与互斥锁配合使用,当某个条件不满足时,线程可以通过条件变量进入等待状态,直到条件满足时被唤醒。
  • 乐观锁(Optimistic Locking):基于假设并发冲突不常发生,在更新数据时检查数据是否已被其他事务修改,适用于读多写少的场景,通过版本号或时间戳等方式实现。
  • 悲观锁(Pessimistic Locking):假设并发冲突可能频繁发生,因此在访问数据前加锁,确保数据在访问期间不会被其他事务修改,适用于写多读少的场景,如数据库的行锁、表锁等。

原子操作是一种不需要显式锁的同步机制,它通过硬件支持的原子操作指令来确保对共享资源的原子访问。虽然原子操作与锁机制都用于解决并发访问的问题,但严格来说原子操作不属于锁机制,不过在实现某些锁(如自旋锁)时通常会使用原子操作来更新锁的状态,以确保在多线程环境下的原子性。

除了锁机制和原子操作,常见的并发控制手段还有信号量、互斥量等。信号量可以控制多个进程或线程对有限资源的访问,通过 wait () 和 signal () 操作来实现资源的申请和释放。互斥量是更接近底层实现的锁机制,一般一次只能被一个线程获取所有权,锁定后的状态也只能被该线程释放

相关文章:

  • 基于javaweb的JSP+Servlet家政服务系统设计与实现(源码+文档+部署讲解)
  • 2900. 最长相邻不相等子序列 I
  • Windows注册表备份与恢复指南
  • AI大模型:(二)2.5 人类对齐训练自己的模型
  • JDK 1.8 全解析:从核心特性到企业实战的深度实践
  • WEB安全--Java安全--LazyMap_CC1利用链
  • 技术更新频繁,团队如何适应变化
  • 使用Python开发经典俄罗斯方块游戏
  • 海外短剧H5/App开源系统搭建指南:多语言+国际支付+极速部署
  • 9. 表的内连和外连
  • 如何在Google Chrome浏览器里-安装梦精灵AI提示词管理工具
  • 记录一下seata后端数据库由mariadb10切换到mysql8遇到的SQLException问题
  • 让三个线程(t1、t2、t3)按顺序依次打印 A、B、C
  • 入门消息队列
  • C# Try Catch Finally 执行顺序是什么?有返回值呢?
  • Google DeepMind 推出AlphaEvolve
  • 解密企业级大模型智能体Agentic AI 关键技术:MCP、A2A、Reasoning LLMs-docker MCP解析
  • 基于matlab实现AUTOSAR软件开发---答疑6
  • 电力电容器故障利用沃伦森(WARENSEN)工业设备智能运维系统解决方案
  • 常用负载均衡技术有哪些?不同网络层面上的网络负载均衡技术
  • 哈马斯官员:若实现永久停火,可交出加沙地带控制权
  • 巴基斯坦与印度停火延长至18日
  • 中办、国办关于持续推进城市更新行动的意见
  • 《求是》杂志发表习近平总书记重要文章《锲而不舍落实中央八项规定精神,以优良党风引领社风民风》
  • 普京召开俄乌谈判筹备会议,拉夫罗夫、绍伊古等出席
  • 科普|男性这个器官晚到岗,可能影响生育能力