Cache的基本原理和缓存一致性

Cache的基本原理和缓存一致性

接触过不少软件工程师,在他们认识里,好像Cache的“透明”的,是硬件工程师的事情,并不怎么关心Cache的行为。但实际上想要进一步提升软件性能、优化系统设计,写出高效的代码,对Cache的理解使用是必不可少。

当然我也是在多次使用并调试过Cache后,才有一点点认识,把之前整理的一些笔记简单分享下,希望能够给需要的人提供一点帮助,另外如果问题之处,还请批评指正。

1 基础知识

1.1 什么是cache

**高速缓存(Cache)**是位于CPU和主存之间的告诉存储单元,专门用于缓存最近使用的数据。

1.2 为什么需要缓存 (Cache)​​

计算机存储系统存在一个基本矛盾:上层越是靠近CPU的存储设备容量越小、速度越快、价格也越贵,而下层越是远离CPU的存储设备容量越大、速度越慢、价格也越便宜。 显然,​​高性能与大容量难以在单一存储层同时实现​​。 为了弥补 CPU 处理速度与主内存(RAM)访问速度之间的巨大差距,缓存 (Cache) 技术应运而生。CPU 缓存通常分为多级,最常见的是 ​​L1(一级缓存)​​、​​L2(二级缓存)​​ 和 ​​L3(三级缓存)​​。下图是现在CPU架构中常见的大小核心设计,一般会设计三级Cache。

​​L1 Cache:​​

最靠近 CPU 核心,速度最快(必须接近 CPU 的时钟频率),成本最高,容量最小(通常在几十 KB 级别)。一般分为​​指令缓存 (I-Cache)​​ 和​​数据缓存 (D-Cache)​​。两者原理类似,但 D-Cache 需要支持读写操作,而 I-Cache 通常只读,因此 D-Cache 的设计更为复杂。使用高速的多端口 SRAM 实现。大容量 SRAM 的查找时间会更长,这限制了 L1 的容量。

​​L2 Cache:​​

一般在一个cluster内部共享,用来加速协调SMP多核的访问速度稍慢于 L1,成本和容量介于 L1 和 L3 之间(通常是几百 KB 到几 MB 级别)。通常是​​指令和数据共享​​的设计。它作为 L1 Cache 和更低层级存储(L3 或内存)之间的缓冲,旨在保存更多近期可能被访问的数据。

​​L3 Cache:​​

位于 L2 Cache 和主内存之间,也可以称为system cache,通常被设计为​​所有核心共享​​的资源,减少访问主内存的次数速度比 L2 Cache ​​更慢​​,但​​远快于主内存​​。容量​​更大​​(通常是几 MB 到几十 MB 级别),单位容量成本相对 L2 更低。

当然上面的解释是显而易见的,下面是更加专业理论的解释为什么需要Cache,如下:

加速访问速度 由于CPU的访问速度远高于主内存,频繁的数据交换会导致瓶颈。Cache通过缓存经常访问的数据减少对内存的依赖,显著加快数据访问速度。利用局部原理 基于时间和空间相关的特性,程序往往重复访问相同或者邻近的数据。Cache能够高效的捕捉这种模式,提高更快的数据响应

NOTE: 时间相关性:如果一个数据现在被访问了,那么在以后很有可能还会被访问 空间相关性:如果一个数据现在被访问了,那么它周围的数据在以后也很有可能被访问。

2 Cache的原理

当然网上有很多硬件大佬对Cache机制原理的分享,我这里算是分享下自己的学习,意在简单理解硬件行为的同时更好的提升软件质量。

Cache主要由两部分组成,Tag RAM和Data RAM。

2.1 高速缓存(Cache)的核心组成​​

高速缓存的物理结构主要由两部分构成:

Tag RAM​​ ​​作用​​:存储缓存行(Cache Line)对应的​​内存地址高位信息​​(Tag)。 ​​工作机制​​:通过比较访问地址的 Tag 部分与存储的 Tag,判断目标数据是否命中缓存。 ​​Data RAM​​ ​​作用​​:存储实际的内存数据块(Cache Data Block)。 ​​设计依据​​:基于​​程序局部性原理​​(时间局部性 + 空间局部性),一次加载连续地址的数据。

另外一个Tag和它对应的所有数据组成的一行称为一个cacheline,是高速缓存操作的最小单位,一般设计在4-128bytes(我是用过的Cache line是32和64bytes的CPU,另外这里说的cacheline大小指的是data block的大小,不包含tag)。 Cache匹配简单流程(以读内存为例)

1、地址拆分​​:访问地址 → [tag] + [index] + [offset]。2、定位Cache line​:用index找到对应的 Cache line。3、定位Cache line filed: 用offset找到在Cache line对应的位置4、tag匹配​​:比较所有Cache line 的Tag与地址的 Tag:

命中(hit)​​:直接读取数据块,使用offset定位具体字节。未命中​(miss)​:从内存加载数据块,替换对应的Cache line(按替换策略)。

2.2 Cache的组成方式

2.2.1 直接映射缓存

Cache 被分成很多行(cacheline),一行可以存储主存的任意一个数据块,数据块大小等于cacheline;每个内存块只能放在Cache的一个固定位置。

举例看下,直接映射缓存的,假如当前使用Cache size是256bytes,8个cacheline,每个cacheline size是32bytes。此时CPU(x32)要访问0x73c这个地址。

tag ram分布

offset: cacheline size是32bytes,所以要访问全部byte需要5bit(2^5=32)index 一共有8个cacheline,所以索引需要3bit(2^3=8)tag 当前是的地址位宽(AW)是32bit,则tag为24bit(32-5-3=24)

举例:地址0x73c解析为

定位流程,如图

1、 取index定位到对应的cacheline2、 取offset定位到对应的byte3、 取tag ram里的tag和地址中的tag比较,如果相同表明该是cache命中(hit),否则miss(说明是其他地址的数据)

另外:

实际上每个cacheline的tag还有一个valid bit,这个bit用来表示cacheline中数据是否有效(1有效;0是无效)。实际在确认hit之前,会检查该位是否有效,有效tag才有意义,否则无效,直接判断失效。data ram最后会有一个dirty bit,用于​​标记数据是否被修改且未同步回主存,在写回策略发挥关键作用,后面在详细说明下。

直接映射缓存在硬件设计上会更加简单,成本上也会较低。根据直接映射缓存的工作方式,我们可以画出主存地址地址和Cache的对应的关系图,如下: 可以看到数据块0-7地址对应数据刚好对应整个Cache,数据块8-f的地址也是同样对应整个Cache。 但这种方式在使用时可能带来以下问题:

1.高冲突 比如数据块0、8都是对应cacheline0,恰好访问在数据0和8之前切换,则会造成Cache 未满,热点数据仍被频繁驱逐,命中率骤降。2.资源利用率低 比如频繁访问数据块3,则cacheline3一直被占用,而其他cacheline一直限制3.性能波动 比如部分程序内存地址分布问题,导致访问冲突率高,会导致性能下降。 以上使得现代处理器很少使用这种方式。

2.2.2 组相连映射缓存

当然组相连就是为了解决直接映射不足,在**组相联缓存(Set-Associative Cache)**中,一个数据可以存储在多个cacheline中,具体位置由组索引(Set Index)决定。如果一个数据可以映射到同一组内的​​n个不同的Cache Line​​,那么这个缓存就称为​​n路组相联(n-way set-associative)缓存​​。

例如: ​​2路组相联​​:每个组(Set)有2个Cache Line,数据可放在其中任意一个。 ​​4路组相联​​:每个组有4个Cache Line,数据可存放在其中任意一个。 这种设计减少了冲突失效(Conflict Miss),提高了缓存命中率,相对增加些硬件复杂度(如并行比较多个Tag)。 原理:

1.地址解析:当CPU请求数据时,首先将地址分解为三部分:标记(Tag)、组索引(Set Index)和偏移(offset)2.组定位:利用组索引确定数据位域哪个组。每个组包含多个缓存行(cacheline)3.标记比较:在确定的组内,CPU比较请求地址的标记和组内tag ram的标记(valid为高时),寻找匹配的缓存行4.命中处理:如果找到匹配的标记,表示缓存命中(hit),则根据偏移直接从缓存中读取数据5.未命中处理:若无匹配,则缓存未命中(miss),需要从主存加载数据到缓存中

根据组相连映射缓存的工作方式,我们可以画出主存地址地址和Cache的对应的关系图,如下:

2.2.3 全相连映射缓存

在**全相连缓存(Fully Associative Cache)**中,存储器中的任意地址可以存放在​​任意一个Cache Line​​中,无需通过索引(Index)定位。此时,地址仅包含​​Tag​​和​​块内偏移(Block Offset)​​,系统会并行比较所有Cache Line的Tag(通常使用​​CAM​​(内容寻址存储器)存储Tag,而Data部分仍用普通SRAM存储)。由于需要全局匹配,全相连缓存的​​灵活性最高​​(无冲突失效),但​​硬件开销大、延迟高​​,因此通常仅用于​​小容量高速缓存​​(如TLB)。 在实际硬件设计中,更常用的是组相连映射方式,缓存的分组策略(特别是​​组相联缓存​​的关联度选择)会紧密围绕 ​​Cache 总容量、访问延迟要求及成本约束​​进行综合权衡:

​​分组(n值)与 Cache 容量的关系​​:

​​小容量 Cache​​(如 L1 Cache:32–64KB):→ 采用 ​​4–8 路组相联​​(如 Intel L1: 8-way)。理由:容量有限时需较高关联度缓解冲突失效(避免频繁替换热点数据)。​​大容量 Cache​​(如 L3 Cache:10–100MB):→ 采用 ​​12–24 路组相联​​(如 AMD Zen4: 16-way,Apple M2: 24-way)。理由:容量本身降低冲突概率,但须控制延迟 → 中等关联度 + 分Bank设计。

L1、L2、L3 Cache常见的分组: ​

L1 Cache​​(延迟敏感):8-way(Intel/ARM)或 4-way(部分嵌入式芯片)。L2 Cache​​(容量/延迟平衡):8–12 way(通用CPU)或 16-way(Apple M系列)。L3 Cache​​(容量优先):12–24 way(通过多组Bank并行降低高关联度延迟)。

实际现在很多设计都会把Cache做成可配的,比如把Cache配置为ram使用,或者一半配成ram另外一半配置为Cache

3 cache更新策略

cache更新策略是指当发生cache命中时,写操作应该如何更新数据。

cache写策略(Write Policy),缓存命中时的写入方式分成两种:写直通((Write-Through)​)和回写(Write-Back)。

写分配策略(Write Allocation Policy), 缓存未命中时的决策分为:写分配(Write-Allocate)和非写分配(Non-Write-Allocate)

读分配策略(Read-Allocation Policy),即缓存未命中时的读取方式分为:读分配(Read-Allocate)和非读分配(Non-Read-Allocate)​

而读命中,一般没有名字,直接从Cache中读取。

下面时Read/Write在Hit/Miss情况下,不同策略的表现行为和优缺点:

行为Hit/Miss类型解释优缺点ReadHit-CPU 直接从 Cache 获取数据。优点:极快(纳秒级延迟)。MissRead Through数据直接从 Main Memory 读取到 CPU,不存入 Cache。优点:避免 Cache 污染。缺点:重复读取相同数据效率低。Read Allocate数据从 Main Memory 加载到 Cache,再从 Cache 读取到 CPU。优点:后续读取相同数据更快。缺点:占用 Cache 空间。No-Read Allocate等同于 Read Through,数据不加载到 Cache。同 Read Through。WriteHitWrite Through数据同时写入 Cache 和 Main Memory。优点:内存数据始终最新(强一致性)。缺点:写入延迟高,带宽压力大。Write Back数据仅写入 Cache,Main Memory 延迟更新(如 Cache 替换时)。优点:写入速度快,带宽占用低。缺点:内存数据可能过期(需额外机制维护一致性)。MissWrite Allocate数据先加载到 Cache,再按 Write Hit 策略处理(Write Through/Back)。优点:适合后续可能重复写入的场景。缺点:额外加载开销。No-Write Allocate数据直接写入 Main Memory,不加载到 Cache。优点:避免无效 Cache 占用。缺点:后续写入无加速。

4 Cache使用问题

4.1 性能问题

const int row = 1024;

const int col = 1024;

int matrix[row][col];

//按行遍历

int sum_row = 0;

for (int r = 0; r < row; r++) {

for (int c = 0; c < col; c++) {

sum_row += matrix[r][c];

}

}

//按列遍历

int sum_col = 0;

for (int c = 0; c < col; c++) {

for (int r = 0; r < row; r++) {

sum_col += matrix[r][c];

}

}

上面是个老生常谈的代码,Cache最小操作单元是cacheline,根据局部性原理,访问主存时会把相邻的部分也加载到Cache里,按行访问的话,后续访问地址相邻的数据时,Cache的命中率就会很高,性能相应按列访问会有不小提升。

4.2 缓存一致性问题

写直达(Write Through) 我们在写内存时,如果是写直达(Write Through)方式,把数据同时写入内存和 Cache 中,那么内存和Cache就直接保持一致,就不会存在一致性问题。 但这样优点就是简单,但缺点是无论数据在不在 Cache 里面,每次写操作都会写回到内存,这样写操作将会花费大量的时间,无疑性能会受到很大的影响。

写回(Write Back) 在写回机制中,当发生写操作时,新的数据仅仅被写入 Cache Block 里,只有当修改过的 Cache Block「被替换」时才需要写到内存中,减少了数据写回内存的频率,这样便可以提高系统的性能。

缓存命中时(数据在 Cache 中)​​

直接更新 Cache 数据标记 Cache Block 为​​脏(Dirty)​​ → 代表与内存不一致但不写入内存

2.缓存未命中时(数据不在 Cache 中)​​ ​​- 若目标 Cache Block 是脏的​​:

先将该 Block 数据​​写回内存​​再加载新数据到 Cache标记为脏 ​- ​若目标 Cache Block 是干净的​​:直接加载新数据到 Cache标记为脏

会发现这种好处是,在大量操作命中Cache的时,大部分时间CPU都不需要读写内存,性能将可以提升很多。

场景1: 单核缓存一致性问题 1、DMA将数据包写入内存mem,CPU(协议栈)需要读取该数据包,但Cache并感知不到内存更新,这是就会导致不一致的问题; 2、同理,CPU(协议栈)组包之后,写入mem,实际只写入了Dcache,并未写入到内存,此时DMA去读取,获取到的也是旧数据。

解决方法: 一般这种单核和外设之间的数据一致性问题,也比较简单,可以通过软件来操作保证缓存一致性: 1、CPU读时数据标记缓存无效,将会把数据先读取到Cache,再从Cache读走。 2、CPU写数据时,刷(flush)Cache操作,将数据从cache刷到mem中。 当然也可以把Cache关了,读写直通(虽然没了一致性问题,但就相当于没了Cache)

场景2: 多核一致性问题 现在的CPU很多都是多核,每个Core都会拥有各自的L1 Cache,每个Cluster也有有自己的L2 Cache,所有的Core也会有L3 Cache,那么就会带来多核的缓存一致性的问题,如果不能保证缓存一致性的问题,就可能给软件带来很多意想不到的错误。 当前的Cluster里共两个core,当core0修改全局地址变量 i = 10;然后缓存到了Cache(并未修改mem),此时core1读取i,获取到的仍然时未修改前的变量。(这里只有L1 是各自core独有的,L2和L3 Cache是俩个core共有)

为了解决这个问题,就要一种机制来同步俩个core 缓存中的数据,这个机制就要做到以下两点: 1、写传播(Wreite Propagation),即:某个core的Cache数据更新时,必须要传播到其他core的Cache 2、事务的串形化(Transaction Serialization),即:某个core里对数据的操作顺序,必须在其他核心看起来顺序是一样的(这里实现需要“锁”机制)

总线嗅探(Snooping) 原理​​:所有缓存通过共享总线监听内存操作(如读写请求),嗅到相关地址时触发本地动作。

MESI​​协议 最经典的协议是MESI

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

Modified,已修改Exclusive,独占Shared,共享Invalidated,已失效

这四个状态来标记cacheline四个不同的状态,即cacheline的状态机。

状态含义监听动作触发M (Modified)数据已修改(仅本核心有效)收到读请求 → 写回内存并转SE (Exclusive)独占(未修改,仅本核有)收到写请求 → 转 MS (Shared)共享(多核只读副本)收到写请求 → 无效化本副本I (Invalid)无效(数据不可用)可加载新数据

简单流程:

当然我这里只是简单介绍了下保证缓存一致性的机制,感兴趣,可以自行搜索深度学习下,作为软件开发,想要提升软件性能,发挥硬件的最大能力,还是要理解这些硬件机制的。

参考: Cache 学习笔记 浅析CPU高速缓存(cache) 一文看懂CPU cache的基本原理

相关推荐

小胖熊辅料配送app
365bet假网站

小胖熊辅料配送app

📅 10-06 👁️ 1358
淮字名字中的寓意及淮字五行属性
365bet中文比分

淮字名字中的寓意及淮字五行属性

📅 08-25 👁️ 1125
关羽一生打了几次败仗?这五人都曾击败过关羽
365bet中文比分

关羽一生打了几次败仗?这五人都曾击败过关羽

📅 08-15 👁️ 4193
qq飞车在哪看结婚记录,QQ飞车结婚系统全解析,手把手教你查看结婚记录与婚姻维护指南
2018俄罗斯世界杯各奖项揭晓 魔笛金球凯恩金靴
365bet中文比分

2018俄罗斯世界杯各奖项揭晓 魔笛金球凯恩金靴

📅 11-22 👁️ 3653
《LOL》S8版本资讯整理一览
365bet中文比分

《LOL》S8版本资讯整理一览

📅 09-22 👁️ 8570
網友票選2024「最美韓國女演員」TOP10!韓孝周第十名,高允貞第二名,第一名是誰呢?
(详细图解)VS2017安装教程
365365bet官

(详细图解)VS2017安装教程

📅 10-26 👁️ 1158
开个直播间要多少钱 做直播前期要投入多少钱
365bet中文比分

开个直播间要多少钱 做直播前期要投入多少钱

📅 08-23 👁️ 3750