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

存储器层次结构——高速缓存存储器

文章目录

      • 高速缓存存储器
        • 层次结构
        • 通用的高速缓存存储器组织结构
        • 直接映射高速缓存
        • 组相联高速缓存

排版较难处理,请务必访问此处阅读

高速缓存存储器

层次结构

高速缓存是介于寄存器和主存储器之间的存储器,L1 高速缓存(一级缓存)的访问速度几乎和寄存器一样快,L1 高速缓存和主存之间又插入了一个更大的 L2 高速缓存,有些现代系统还包括一个更大的位于 L2 高速缓存和主存之间的 L3 高速缓存。

:::color1
高速缓存既保存数据,也保存指令。

  • $ i-cache $:只保存指令的高速缓存。通常是只读的。
  • $ d-cache $:只保存程序数据的高速缓存。
  • 统一的高速缓存:既保存数据,也保存指令的高速缓存。

:::

在$ Intel Core i7 处理器的高速缓存层次结构中,每个 C P U 芯片有 4 个核,,每个核有私有的 的高速缓存层次结构中,每个 CPU 芯片有 4 个核,,每个核有私有的的高速缓存层次结构中,每个CPU芯片有4个核,,每个核有私有的L1 i-cache、 、L1 d-cache $和 L2 统一的高速缓存。所有的核共享片上 L3 统一的高速缓存。

这个层次结构的一个有趣的特性是所有的 SRAM 高速缓存存储器都在 CPU 芯片上。

通用的高速缓存存储器组织结构

核心组织维度 (S, E, B)

一个通用高速缓存的结构可以用一个元组来完全确定:

  • 组数 ():整个高速缓存被划分为个互不相交的“组”(Set),从组0一直到组。组索引需要个比特来定位。
  • 行数 ():每一个组内包含个“行”(Line)。根据的取值不同,高速缓存可以分为直接映射高速缓存()、组相联高速缓存()和全相联高速缓存(只有一个组,)。
  • 块大小 (字节):每一行中包含一个高速缓存块(Cache Block),存储着真正从主存中复制过来的连续数据,大小为字节。块偏移需要个比特来定位。
高速缓存行的内部组成

高速缓存中的每一行都由三个关键部分组成:

  • 有效位(Valid bit):每行有个有效位。用来指示这一行是否包含有效的数据(1表示有效,0表示无效)。
  • 标记位(Tag bits):每行有个标记位(其中为主存地址的总位数)。因为多个主存地址可能会映射到同一个缓存组,标记位用来唯一确定当前缓存块中存储的到底是哪一个内存块。
  • 数据块(Data Block):即图中的。它是实际存放数据的地方,共个字节。
高速缓存总容量计算

高速缓存的总大小(Capacity,通常指能够存储的纯数据容量,不包括有效位和标记位等开销)由公式决定:字节,即:总容量 = 组数每组的行数每块的字节数

图中的硬件地址结构是 CPU 寻址高速缓存(Cache)的核心机制。一个系统拥有 $ m $ 位主存地址,当 CPU 试图从主存中读取一个字时,它不会直接把整个地址发送给主存,而是将这 $ m $ 位地址划分成三个逻辑字段。

地址结构的字段描述
  • 块偏移(Block Offset):
    • 含义:用来在选中的高速缓存块(Cache Block)中定位目标数据的具体字节位置。
    • 对应关系:由于高速缓存中每个数据块包含字节,因此需要地址的低位作为偏移量。例如,若字节,则需要位(可表示偏移量 0, 1, 2, 3)。
  • 组索引(Set Index):
    • 含义:用来决定该内存地址映射到高速缓存中的哪一个“组”(Set)。
    • 对应关系:通用高速缓存被划分成个组。地址中间的这位相当于一个数组的下标,告诉硬件应该去组 0 到组中的哪一组去查找数据。
  • 标记(Tag):
    • 含义:用来唯一标识映射到同一个高速缓存组的不同内存块。
    • 对应关系:主存的大小远远大于高速缓存,这意味着会有很多不同的主存块映射到同一个高速缓存组中。地址的高位(其中)会与高速缓存行中的“标记位”进行比对,以确定当前缓存行里存的数据到底是不是 CPU 想要的那个内存块。
地址结构与高速缓存结构之间的动态联系(工作原理)

当 CPU 携带这个位地址去高速缓存中查找数据时,两者的结构通过以下三个步骤产生联系,实现“缓存寻址与命中判定”:

第一步:组选择(利用组索引位)

硬件抽取地址中间的位,作为索引定位到高速缓存对应的

  • 结构联系:地址的位直接对应高速缓存的个组。由于硬件需要并行访问,组索引就像一个多路复用器的选择信号,瞬间帮 CPU 在纵向上筛选出具体某一个“组”空间。
第二步:行匹配(利用标记位与有效位)

进入选定的组后,硬件检查该组内的所有。对于每一行,硬件会同时做两件事:

  1. 检查该行的有效位(Valid bit)是否为 1。
  2. 将地址中的标记,与该行硬件结构里自带的标记字段进行相等性比较。
  • 结构联系:如果组内某一行同时满足“有效位为1”且“标记完全匹配”,这就是一次缓存命中(Cache Hit)。这说明主存中的那个数据块此时此刻正躺在这一个高速缓存行里。如果找不到满足条件的行,就是缓存缺失(Cache Miss)
第三步:字抽取(利用块偏移位)

一旦确定缓存命中,硬件就会来到该行对应的数据块(Data Block)中。

  • 结构联系:数据块在结构上是一个从字节的数组。地址底部的位块偏移正是这个数组的下标。硬件根据位的数值,精确地从字节的数据块中抽取出 CPU 请求的那个字节或字(Word),并返回给 CPU。
| ** 基本参数** | | | --- | --- | | ** 参数** | ** 描述** | | $ S=2^s $ | 组数 | | $ E $ | 每个组的行数 | | $ B=2^b $ | 块大小(字节) | | $ m=\log_2(M) $ | (主存)物理地址位数 |
衍生出来的量
参数描述
$ M=2^m $内存地址的最大数量
$ s=\log_2(S) $组索引位数
$ b=\log_2(B) $块偏移位数
$ t=m-(s+b) $标记位数
$ C=B \times E \times S $不包括像有效位和标记位这样开销的高速缓存大小(字节)
直接映射高速缓存

根据每个组的高速缓存行数 E,高速缓存被分为不同的类。每个组只有 1 行(E=1)的高速缓存称为直接映射高速缓存。

直接映射高速缓存确定一个请求是否命中,然后抽取出被请求的字的过程
  1. 组选择
  • 核心操作:CPU 从主存地址中抽取出中间的位组索引。
  • 硬件行为:将这位组索引视为一个无符号数,作为高速缓存组数组的下标,直接定位到对应的组(在多个组中纵向筛选出目标组)。

  1. 行匹配
  • 核心操作:在选中的组中,检查是否存在一行满足命中条件。
  • 判定条件:必须同时满足以下两个条件:
    1. 该行的有效位(Valid bit)必须为 1
    2. 该行硬件结构中的标记位(Tag)必须与地址中的位标记完全相等。
  • 结果:若同时满足则缓存命中(Cache Hit),进入字选择;若都不满足,则为缓存缺失(Cache Miss)。

  1. 字选择
  • 核心操作:在命中的那一行中,定位并提取出 CPU 请求的具体字节或字。
  • 硬件行为:将地址中低位的位块偏移视为字节数组的下标。由于高速缓存块是一个从字节的数组,硬件根据 $b$ 位的数值,精准抽取出请求的起始字节并返回给 CPU。
  1. 行替换
  • 核心操作:当发生缓存缺失(且需要将新数据读入该组)时,如果该组内已经没有空闲行(有效位均为 1),就必须腾出空间。
  • 策略机制:硬件依据替换策略(如LRU 最久未使用算法、LFU 最少使用算法)在当前组内选择一个“牺牲行”将其驱逐。若该牺牲行是脏数据(被修改过),需写回主存;随后将新读入的内存块写入该行,并更新标记位和有效位。
  • 注:对于直接映射高速缓存(),由于每组只有一行,无需策略,直接覆盖即可。
  1. 冲突不命中
  • 核心操作:这是一种特殊的缓存缺失现象(又称碰撞缺失)。
  • 发生原理:当程序的访问模式导致多个不同的主存块被频繁映射到同一个高速缓存组时,即便高速缓存的总空间还非常充裕,这些块也会在这个特定的组里交替发生行替换,互相将对方驱逐(俗称“缓存抖动”抖动,Cache Thrashing),导致连续访问时持续不命中。
:::color4 ⚠️** 为什么用中间的位来做索引?**

高速缓存之所以选择中间的位(组索引 $ s $ 位)而不是高位来做索引,是为了最大化利用局部性原理,减少冲突不命中

:::

如果使用“高位”做索引

:::danger

  • 映射规律:高位相同的连续内存块会被映射到同一个缓存组中。
  • 致命缺陷:程序在运行时通常具有空间局部性(即倾向于顺序访问连续的内存块,如数组、连续的代码指令)。
  • 结果:如果用高位索引,当程序顺序读取0000,0001,0010,0011这四个相邻的块时,它们全都会争夺同一个组(组 00)。这会导致该组发生严重的频繁替换(缓存抖动),而剩下的组 01、10、11 却完全闲置。

:::

使用“中间位”做索引

:::color1

  • 映射规律:中间位做索引时,相邻的内存块会被均匀、交错地散列到不同的缓存组中(如0000到组00,0001到组01,0010到组10…)。
  • 巨大优势:当程序具有空间局部性、顺序访问一整块连续内存时,这些块会依次存入不同的缓存组
  • 结果:4个组都能被同时、均衡地利用起来,连续的内存访问绝不会在同一个组内“打架”,从而大幅降低了冲突不命中率

:::

组相联高速缓存

一个$ 1<E<C/B $的高速缓存通常称为 E 路组相联高速缓存。

比如 2 路组相联高速缓存:

组相联高速缓存确定一个请求是否命中,然后抽取出被请求的字的过程

1. 组选择

  • 机制:CPU 抽取主存地址中的位组索引。
  • 过程:将这位无符号数作为组数组的下标,定位到对应的一个高速缓存组。由于它是组相联的,这一步在纵向上筛选出包含了个可选行的“目标组”。

2. 行匹配和字选择

  • 行匹配(并行检索)
  • 过程:硬件会同时(并行地)检查该选定组中的所有个行。
  • 命中判定:对于任意一行,必须同时满足“该行的有效位为 1”且“该行的标记位(Tag)与地址中的位标记完全一致”。如果找到这样的一行,即为缓存命中(Cache Hit)
  • 字选择(数据抽取)
  • 过程:一旦行匹配成功,硬件利用地址中的低位块偏移作为字节数组的下标,从该行的数据块(包含字节)中精确抽取出 CPU 请求的字节或字,并将其返回。
  • 注:如果组内没有任何一行的标记能匹配成功,或者匹配成功的行有效位为 0,则为缓存缺失(Cache Miss),进入第三阶段。

3. 行替换

  • 触发条件:当发生缓存缺失,需要从主存中将包含目标字的数据块加载到当前组时触发。
  • 替换逻辑
  • 有空闲行:如果该组内存在有效位为 0 的空闲行,则直接将新块写入该空闲行,设置有效位为 1,并填入相应的标记。
  • 无空闲行(需要驱逐):如果整个组的个行都满了(有效位全为 1),硬件必须根据既定的替换策略(如LRU 最久未使用算法、LFU 最少使用算法)挑选出一个“牺牲行”。
  • 写入与更新:将牺牲行的数据腾空(若该行为脏数据,需先写回主存),随后把从主存读入的新数据块写入该行,并更新其标记位。
#### 全相联高速缓存 全相联高速缓存是由一个包含所有高速缓存行的组(即$ E=C/B $)组成的。组相联高速缓存确定一个请求是否命中,然后抽取出被请求的字的过程

1. 组选择

  • 机制:由于整个高速缓存结构中只有一个组(组 0),因此主存地址中不需要专门的组索引位()。
  • 过程:CPU 根本不需要进行任何计算或检索,硬件默认且必须总是直接选择组 0

2. 行匹配和字选择

  • 行匹配(全并行检索)
  • 过程:因为所有的数据块都挤在同一个组里,硬件必须同时(并行地)对组 0 内的所有高速缓存行进行检索。
  • 命中条件:硬件会逐一比对。必须有一行同时满足:
  1. 有效位必须被设置(即有效位 = 1)。
  2. 高速缓存行中某一行的标记位必须与地址中的位标记位相匹配

  • 字选择(数据抽取)
  • 过程:如果上述 1 和 2 的条件同时满足,即为缓存命中(Cache Hit)
  • 抽取:硬件接着利用地址底部的位块偏移作为该行数据块()的起始字节下标,精准地抽取出 CPU 请求的字或字节。
  • 注:如果遍历完组内所有的行,都没有找到“有效”且“标记匹配”的行,则判定为缓存缺失(Cache Miss),随后会触发第三阶段的行替换。
#### 高速缓存参数的性能影响 | ** 特征维度** | ** 直接映射高速缓存(Direct-Mapped)** | ** 组相联高速缓存(Set-Associative)** | ** 全相联高速缓存(Fully Associative)** | | --- | --- | --- | --- | | ** 组数 (**$ S $** )** | $ S = C / B > 1 $ (有多个组) | $ S = C / (E \times B) > 1 $ (有多个组) | $ S = 1 $ (只有一个组) | | ** 每组行数 (**$ E $** )** | $ E = 1 $ (每组只有一行) | $ E = 2, 4, 8, 16 \dots $ (每组多行) | $ E = C / B $ (所有行都在一组内) | | ** 地址中的组索引位 (**$ s $** )** | 有 $ s = \log_2(S) $ 位 | 有 $ s = \log_2(S) $ 位 | ** 无** ($ s = 0 $ 位,不需要组索引) | | ** 行匹配/检索方式** | 硬件只需根据组索引检查** 唯一的一行** | 硬件必须** 并行(同时)** 检索选中组内的 ** $E$**** 个行** | 硬件必须** 全并行(同时)**** 检索整个 Cache 的**** 所有行** | | ** 块替换策略** | 不需要(新块直接覆盖旧块) | 需要(如 LRU、LFU 等算法) | 需要(如 LRU、LFU 等算法) | | ** 硬件成本与复杂度** | ** 极低** (索引定位快,不需要复杂的并行比较器) | ** 中等** (需要在组内进行小规模的并行比较) | ** 极高** (需要大量庞大的并行相联比较器) | | ** 冲突不命中率** | ** 最高** (极易发生抖动) | ** 中等/低** (行数越多,冲突越少) | ** 最低** (空间利用率最高,基本消除了冲突不命中) | | ** 访问速度** | ** 最快** (命中判定结构最简单) | ** 中等** | ** 最慢** (全并行大规模检索延迟较高) | | ** 典型应用场景** | ** 大容量的低级缓存** (如 L3 Cache),或对硬件成本/功耗极其敏感的嵌入式处理器中。 | ** 现代 CPU 的主流缓存** (如 L1、L2 Cache)。在速度、成本和命中率之间取得了完美的平衡。 | ** 容量极小但要求绝对命中** 的致命核心组件(如 ** TLB 快表** 、虚拟内存的页表缓存)。 |

:::danger
一些衡量高速缓存性能的指标

  • 不命中率:在一个程序执行或程序的一部分执行期间,内存引用不命中的比率。$ 不命中数量/引用数量 $。
  • 命中率:命中的内存引用比率。$ 1-不命中率 $
  • 命中时间:从高速缓存传送一个字到 CPU 所需的时间,包括组选择、行确认和字选择的时间。
  • 不命中处罚:由于不命中所需要的额外的时间。

:::

有关写的问题

当 CPU 执行写操作(修改数据)时,如何保持高速缓存(Cache)与低一层存储(如主存)之间的数据一致性

这个问题需要分写命中写不命中两种情况来讨论,并结合产业界的最优解进行总结:

1. 写命中(Write Hit)

当 CPU 准备写入一个字,且该字所在的块已经缓存在 Cache 中时,有两种更新策略:

  • 直写(Write-through)
    • 机制:立即将的副本写回到低一层(如主存)中。
    • 优缺点:最简单,但缺点极其明显。每次写操作都会引发一次总线事务,产生巨大的总线流量,拖慢系统速度。
  • 写回(Write-back)
    • 机制:尽可能地推迟更新。只更新当前 Cache 行中的副本,而不立即写回低一层。只有当替换算法判定这一行需要被驱逐(替换)时,才把它写回低一层。
    • 硬件开销:每一行必须额外维护一个修改位/脏位(Dirty bit),用来表明这个缓存块是否被修改过。
    • 优缺点:显著减少了总线流量,但增加了硬件实现的复杂性。

2. 写不命中(Write Miss)

当 CPU 准备写入一个字,但该字所在的块不在 Cache 中时,同样有两种处理方式:

  • 写分配(Write-allocate)
    • 机制:先从低一层加载包含的块到 Cache 中,然后更新这个缓存块。
    • 设计意图:试图利用接下来的空间局部性(之后可能还会读写这个块内的其他数据)。
  • 非写分配(Not-write-allocate)
    • 机制:避开 Cache,直接把这个字写到低一层存储中,不把数据块加载到 Cache 里。
http://www.jsqmd.com/news/938376/

相关文章:

  • AI驱动网络安全实战:从威胁检测到自动化响应的架构与挑战
  • ASR6601 LPWAN SoC开发实战:从硬件解析到LoRaWAN协议集成
  • 别再让电机乱转了!用Arduino Mega2560 + TB6612驱动MG513,手把手教你实现精准PWM调速与正反转控制
  • 语料蒸馏:从海量文档到结构化知识资产的工程实践
  • 手把手教你用MetaMask创建钱包并获取免费测试币(从安装到第一笔转账)
  • 如何用AI视觉语言模型UI-TARS-desktop实现自然语言控制电脑?
  • 从飞机上网到水下机器人:盘点LiFi(可见光通信)那些意想不到的硬核应用场景
  • Confluence CVE-2023-22527漏洞修复指南:从影响分析到升级/缓解方案
  • 当He-Ne激光遇上金属棒:手把手教你用干涉法‘看见’热膨胀,并理解其背后的物理图像
  • C/C++ 基础笔记(五)
  • PCB布线别再瞎画了!从趋肤效应到集肤深度,手把手教你搞定10MHz以上信号完整性问题
  • 用GD32F3x0单片机驱动TDC-GP22(SSP1922)做高精度测距:一份完整的SPI通信与寄存器配置指南
  • 电阻式与电容式土壤湿度传感器对比:原理、校准与物联网应用实践
  • SQL学习日志 Day_3 :(SELECT查询语句入门)
  • Arduino避障小车:从HC-SR04超声波传感器到L293D电机驱动的完整实现
  • 量子门分解与校准技术详解
  • mpv.net 终极指南:Windows平台高性能媒体播放器完整配置与实战技巧
  • 华硕笔记本终极控制方案:5分钟掌握G-Helper轻量级优化工具
  • SAP生产计划员必看:如何利用组件与装配报废率,精准控制原材料采购数量?
  • 基于ESP-01F与WebSocket的智能温度计:物联网开发实战指南
  • IDEA装了LiteFlowX插件后,我写规则文件再也没翻过文档(智能提示+跳转真香)
  • 手把手教你用AWR2944开发板配置DDMA波形:从Lua脚本到Matlab数据处理全流程
  • 别再只看风速了!固定翼新手选飞行天气,这3个APP和2个关键数据更重要
  • 基于 Harmony 6.0 应用的同城活动组织平台首页实现
  • 如何5分钟搭建个人音乐库:洛雪音乐聚合音源终极指南
  • FastReport WPF 2024.1.3实战:5分钟搞定从数据库到PDF报表的完整流程
  • 基于树莓派的智能迷你冰箱:物联网全栈开发与硬件实践
  • IPXWrapper完整指南:让Windows 10/11完美运行经典游戏联机
  • 不到150元成本!基于STM32的智能手表项目复盘:从PCB布线到低功耗设计的避坑经验
  • 鸣潮模组终极指南:15+功能解锁,彻底改变你的游戏体验