本人是初学者,所以文章内容多来自专业书籍和他人博客,再加上一些自己的理解,如果有误欢迎指出:D
目录
写分配/读分配(Write-Allocate/Read-Allocate)
一些概念
局域性原理
指令和数据会在内存中连续存放,同时有部分指令和数据经常会被CPU调用,
指令预取
路预测
cache一致性/缓存一致性
MESI缓存一致性协议
inclusive和exclusive
inclusive cache指的是某一地址的数据可能存在多级缓存中。与inclusive cache对应的是 exclusive cache,这种cache保证某一地址的数据缓存只会存在于多级cache其中一级。
Cache的组成
Cache功能:加速数据访问。
缓存行也就是块
Cache的大小被称为Cache size,表示Cache可以缓存最大数据的大小。将cache平均分成相等的很多块,每一个块大小称之为Cache line,也就是缓存行。缓存行是Cache和主存之间数据传输的最小单位,指的是当CPU试图load一个字节数据的时候,如果cache缺失,那么cache控制器会从主存中一次性的load cache line大小的数据到cache中。例如,cache line大小是8字节。CPU即使读取一个byte,在cache缺失后,cache会从主存中load 8字节填充整个cache line。
- 通常使用64Byte大小的缓存行。
- 主存 块 和 缓存行 的大小是一致的,块内的字数(Byte)是相等的。所以下图中主存块的块内地址位宽和缓存行中块内地址的位宽是一致的。但因为主存和Cache的大小不同,所以高位用来表示编号的位宽不同。
Cache line的组成
以下图为例,表示的是64 Bytes大小的cache,并且cache line大小是8字节。标签(Tag):标识该缓存行对应的物理地址的一部分,用于确认缓存命中与否。其中红框框住的部分就是一个Cache line所包含的内容。
- 有效位(Valid Bit):表示该缓存行中的数据是否有效。
- 标签(Tag):标识该缓存行对应的物理地址的一部分,用于确认缓存命中与否。
- 数据块(Data Block):实际存储的数据部分。
- 脏位(Dirty Bit):标记该缓存行中的数据是否已经被修改但尚未写回到主存。(和cache更新策略有关,只和写操作有关)
一般来说cache的大小指的是所保存的数据规模,并不包括其中需要储存tag的物理空间。
在缓存(Cache)系统中,为了高效地管理和检索数据,缓存通常被组织成多个组件或“数组”,其中最重要的两个是数据阵列(Data Array)和标签阵列(Tag Array)。
如何根据地址找到Cache中的对应数据
我们一共有8行cache line(分了8块),cache line大小是8 Bytes。所以我们可以利用地址低3 bits(如上图地址蓝色部分)用来寻址8 bytes中某一字节,我们称这部分bit组合为offset(offset查找cache line中的目标字节)。同理,8行cache line,为了覆盖所有行。我们需要3 bits(如上图地址黄色部分)查找某一行,这部分地址部分称之为index(index用来查找目标cache line)。现在我们知道,如果两个不同的地址,其地址的bit3-bit5如果完全一样的话,那么这两个地址经过硬件散列之后都会找到同一个cache line。所以,当我们找到cache line之后,只代表我们访问的地址对应的数据可能存在这个cache line中,但是也有可能是其他地址对应的数据。所以,我们又引入tag array区域,tag array和data array一一对应。每一个cache line都对应唯一一个tag,tag中保存的是整个地址位宽去除index和offset使用的bit剩余部分(如上图地址绿色部分)。tag、index和offset三者组合就可以唯一确定一个地址了。因此,当我们根据地址中index位找到cache line后,取出当前cache line对应的tag,然后和地址中的tag进行比较,如果相等,这说明cache命中。如果不相等,说明当前cache line存储的是其他地址的数据,这就是cache缺失。
在比较tag来判断是否相等之前会先判断有效位(Valid Bit)是否有效,如果无效的话就直接判断cache缺失,不用再执行下一步比较tag确认cache line是否命中。
- 当系统刚启动时,cache中的数据都应该是无效的,因为还没有缓存任何数据。cache控制器可以根据valid bit确认当前cache line数据是否有效。
Cache和主存的映射关系
为什么不能直接拿着主存地址去cache中找数据?
- 个人理解:因为局域性原理,所以cache只会存放主存中的某段地址范围内的数据,段和段之间的地址是不连续的,所以需要有一种转换机制,把分段的主存地址映射成连续的cache地址
- 当我们谈论“Cache地址”时,实际上是指如何在Cache中定位存储的数据块(即缓存行或块),而不是像主存那样有一个线性的、连续的地址范围 所以上面部分的理解可能有出入
- 视频讲解:因为cache的大小肯定是比主存要小的,这样对应cache的地址位宽肯定也会比主存的地址位宽要短,所以必须要执行转换
三种映射方式
- 直接映射缓存(Direct-Mapped Cache)
- 全相联缓存(Fully Associative Cache)
- N路组相联缓存(N-way Set Associative Cache)
路Way和组Set的概念
组(Set):在N路组相联缓存中,整个缓存被划分为多个组(Sets)。每个组包含若干个缓存行(Cache Lines),这些缓存行的数量被称为“路数”(Ways)。
一个组中的缓存行cache line的index值都是一样的。
N路组相联缓存的工作原理
N路组相联:假设缓存有N路(N-way),表示每个路组中有N个缓存行。(每个set中有几个缓存行,就是几路组相联)
假设有一个N路组相联缓存,其工作流程如下:
-
地址划分:
- 地址通常被划分为三部分:标签(Tag)、索引(Index)和偏移量(Offset)。
- 标签(Tag):标识缓存行对应的实际物理地址。
- 索引(Index)/组索引(Set Index):用于定位特定的组。(索引的定义相比前文发生了变化)
- 偏移量(Offset):用于在缓存行内部定位具体的字节或字。
- 地址通常被划分为三部分:标签(Tag)、索引(Index)和偏移量(Offset)。
-
查找缓存:
- 使用地址的索引部分找到特定的组。
- 在该组内,比较所有缓存行的标签,以确定是否存在匹配项(即缓存命中)。
- 根据需求来使用偏移量offset确定目标字节
-
缓存命中/未命中处理:
- 如果找到匹配的标签,则认为发生了缓存命中,可以直接从该缓存行读取数据。
- 如果没有找到匹配项,则发生缓存未命中,需要从主存加载相应的数据块,并可能替换当前组中的某个缓存行。
示例:两路组相联
此时缓存会被分为2路(根据 两路组相联) ,也就是将原本64Byte的缓存分成了平均分成两份32Byte。又因为Cache line size是8Byte,所以每一份有4个组。
cache line size没有变化,所以offset还是3bit(蓝色部分),因为上文提到每个组中的index是一致的,4组需要用到2bit的index(黄色部分)。(是否意味着N路组相联中的N也可以用来表示index的位宽?)
图中跟据地址中的index找到了第2行cache line(从上到下,从0开始),也就是箭头所指区域。先根据index找到set,然后将组内的所有cache line对应的tag取出来和地址中的tag部分对比,如果其中一个相等就意味着命中。
直接映射和两路组相联的对比
直接映射
两路组相联
在两路组相联的情况下,继续以程序访问地址0x00、0x40、0x80为例,此时这几个地址的index还是一样的,不过在两路的情况下,0x00地址的数据可以被加载到way 1,0x40可以被加载到way 0,这样两个地址的数据都可以被缓存在cache中。如果拓展成4路组相联的话,0x80的地址同样可以放在cache中。
因此,当cache size一定的情况下,组相连缓存对性能的提升最差情况下也和直接映射缓存一样,在大部分情况下组相连缓存效果比直接映射缓存好。同时,其降低了cache颠簸的频率。从某种程度上来说,直接映射缓存是组相连缓存的一种特殊情况,每个组只有一个cache line而已。因此,直接映射缓存也可以称作单路组相连缓存。
全相联缓存
当所有cache line都放在一个组内时,就叫做全相联缓存。由于不再区分组,所以此时也不再需要index。所以需要将地址中的tag部分与每一个cache line中的tag进行逐一比对。在全相连缓存中,任意地址的数据可以缓存在任意的cache line中。所以,这可以最大程度的降低cache颠簸的频率。但是硬件成本上也是更高。
Cache颠簸
指在计算机系统中,由于缓存容量不足或缓存替换策略不当等原因,导致频繁地进行缓存行的替换操作,从而使得缓存命中率显著下降的现象。这种现象会严重降低系统的性能,因为过多的缓存未命中会导致大量的内存访问请求,而内存访问的速度远慢于缓存访问。
实例说明
假设有一个程序需要处理一个非常大的数组,而该数组的大小超出了缓存的容量。当程序遍历这个数组时,由于缓存无法容纳整个数组的所有数据,因此每次只能加载一部分数据进入缓存。随着程序继续执行,新的数据不断替换旧的数据进入缓存,导致之前已经加载的数据很快就被驱逐出去。当程序再次需要访问这些已被替换出去的数据时,又必须从主存重新加载,这就形成了缓存颠簸,严重影响了程序的执行效率。
Cache分配策略
只有在CPU尝试读取或写入数据时出现缓存缺失(Cache Miss),才会触发对应的缓存分配策略。缓存分配策略决定了当发生缓存未命中时如何从主存加载数据并更新缓存内容。
- CPU发出读/写请求
- 根据地址去缓存中查找数据,但缓存缺失
- 执行cache分配策略
- 需要对主存操作
写分配/读分配(Write-Allocate/Read-Allocate)
这是最常见的策略,即在发生读缺失或写缺失时,自动从主存加载相应的数据块到缓存中。
- 读分配:从主存读取所需的数据块并将其加载到cache line中。(默认cache支持读分配)
- 写分配:首先从主存中加载数据块到cache line中(相当于先做个读分配动作),然后会更新cache line中的数据。
不支持写分配(No-Write Allocate)
对于写缺失情况,系统不会将数据存入缓存中,而是直接写入主存。这种方式适用于写操作频繁但不需要缓存的情况。
比如流式数据:
特点:数据只被写入一次,后续不会被重复访问。
示例:视频编码中的帧缓冲区写入、网络数据包传输。
原因:加载到Cache中无意义,反而会污染Cache。
写分配的操作流程
当CPU发出一个写请求且该地址不在缓存中时,写分配策略会按照以下步骤操作:
- 检测写缺失:CPU尝试写入一个地址,但发现该地址对应的数据块不在缓存中(即发生写缺失)。
- 从主存加载数据块:由于采用写分配策略,系统会从主存中读取整个数据块,并将其加载到缓存中。即使CPU只需要修改其中的一部分数据,也会加载整个块。
- 更新缓存中的数据:数据块被加载到缓存后,CPU会在缓存中执行预期的写操作,更新相应的数据。
- 根据写策略更新主存:根据所采用的具体写策略(写回或直写),决定何时将更新后的数据写回到主存。
Cache更新策略
cache更新策略是指当发生cache命中时,写操作应该如何更新数据。cache更新策略分成两种:写直通和回写。
- 写直通(Write-through):每次对缓存进行写操作的同时也立即更新主存中的相应位置。
- 写回(Write-back):只有在该缓存行被替换出缓存时才将其写回到主存。这期间如果缓存行再次被修改,则只需更新缓存中的副本。
写直通(write through)
当CPU执行store指令并在cache命中时,我们更新cache中的数据并且更新主存中的数据。cache和主存的数据始终保持一致。
写回(write back)
当CPU执行store指令并在cache命中时,我们只更新cache中的数据。并且每个cache line中会有一个bit位记录数据是否被修改过,称之为dirty bit。我们会将dirty bit拉高。主存中的数据只会在cache line被替换或者显示的clean操作时更新。因此,主存中的数据可能是未修改的数据,而修改的数据躺在cache中。cache和主存的数据可能不一致。
写缓冲器
写缓冲器Write Buffer,由SRAM实现。主要目标:解耦CPU写操作与主存访问的延迟,允许CPU在数据尚未写入主存时继续执行后续指令。
写直通模式
每次写操作都需要同时写入Cache和主存,这时候可能会有延迟问题。写缓冲器的作用就是在这里,暂存这些需要写入主存的数据,让CPU不用等待主存写入完成就可以继续执行,从而提高效率。
写回模式
假设有一个场景,其中CPU正在执行一系列写操作,并且其中一个写操作触发了Cache Line的替换:
- 当Cache决定替换一个包含脏数据的缓存行时,它会将这条缓存行的地址和数据放入写缓冲器。
- 同时,Cache可以立即开始加载新的数据到腾出的空间中,而不需要等待写缓冲器中的数据完全写入主存。
- 写缓冲器随后会在后台异步地将数据写入主存,释放缓冲器空间以便处理后续的写操作。
写分配写回示例
假设我们有一个64 Bytes大小直接映射缓存,cache line大小是8 Bytes,采用写分配和写回机制。当CPU从地址0x2a读取一个字节,cache中的数据将会如何变化呢?假设当前cache状态如下图所示(tag旁边valid一栏的数字1代表合法。0代表非法。后面Dirty的1代表dirty,0代表没有写过数据,即非dirty)。
根据index找到对应的cache line,对应的tag部分valid bit是合法的,但是tag的值不相等,因此发生缺失。此时我们需要从地址0x28地址开始(不是题目提供的0x2a,请注意cache line大小8Byte对齐)加载8Byte数据到该cache line中。但是,我们发现当前cache line的dirty bit置位。(说明这个缓存中地址所存放数据和内存中的数据是不一样的,此时这个地址不是我们要的0x2a,而是后面计算出的0x128(未对齐前是0x12a))因此,cache line里面的数据不能被简单的丢弃,(因为脏位说明这个数据被改过)而是采用写回机制,将cache line的数据0x11223344写到内存起始地址0x0128区域(这个地址根据tag中的值及cache line所处的行计算得到 (0x04,101,010))。这个过程如下图所示。
当写回操作完成,我们将主存中0x28地址开始的8个字节加载到该cache line中,并清除dirty bit。然后根据offset找到0x52返回给CPU。
读写操作图示
Cache的替换策略
前面我们提到了,当出现缓存缺失,也就是在Cache中找不到目标数据时,会执行Cache的分配策略,即要将缺失的内容从主存中调入到Cache里。这样有可能会出现一个问题,如果Cache满了,就没办法将主存块内容直接调入Cache,此时就得采用Cache的替换策略。
Cache根据替换算法来决定要将哪个Cache line移回到主存中,从而腾出空位接收新的主存块。
先进先出FIFO算法
选择最早调入的Cache line进行替换,但因为没有考虑到局域性原理,所以命中率不会提升。
近期最少使用LRU算法
根据局域性原理,选择近期内没有访问过的Cache line进行替换。
随机法
随机替换Cache line
Cache的组织方式
PIVT,VIPT,VIVT,PIPT
歧义(ambiguity)
指不同的数据在cache中具有相同的tag和index。
别名
不同的虚拟地址映射相同的物理地址,而这些虚拟地址的index不同,此时就发生了别名现象(多个虚拟地址被称为别名)。
L1,L2,L3 Cache
每一个核中都有单独的指令Cache和数据Cache(也叫I-Cache和D-Cache)
Cacheable和Non-Cacheable
系统中的地址支持两种内存属性区域,分别是Memory属性和Device属性。其中Memory属性的地址支持cacheable和non-cacheable,而Device属性地址只支持non-cacheable,这两种有什么区别呢?
Cacheable(可缓存)
-
定义:
CPU 访问该内存时,允许将数据缓存在高速缓存(Cache)中。后续访问时,CPU 优先从缓存读取数据,减少访问主存的次数,从而提升性能。 -
特点:
- 高性能:缓存速度远快于主存,适合频繁访问的数据(如代码、堆栈)。
- 潜在风险:缓存与主存中的数据可能不一致(需硬件或软件维护一致性)。
-
适用场景:
- 程序代码、堆栈、全局变量等频繁读写的内存区域。
- 单核系统或多核系统(需硬件支持缓存一致性协议,如 MESI)
Non-Cacheable(不可缓存)
-
定义:
CPU 访问该内存时,直接读写主存,绕过缓存。每次访问都是实时的,确保数据一致性,但性能较低。 -
特点:
- 实时性:数据修改立即可见(如写入硬件寄存器)。
- 一致性:避免缓存与主存数据不一致的问题。
- 低性能:每次访问需直接操作主存,速度较慢。
-
适用场景:
- 外设寄存器(如 GPIO、UART 控制寄存器)(数据只用一次,或是需要修改寄存器参数配置)。
- DMA 缓冲区(设备直接读写内存,需与 CPU 数据一致)(DMA功能,是在存储器和存储器,或是存储器和外设,外设和外设间搬运数据,需要保证搬运的数据是最新且正确的)。
- 多核共享内存(无硬件缓存一致性支持时)
Bufferable
Buffer:平衡速度差异或处理数据流
AXI种axcache信号的值如何决定
参考链接
深入理解cache - 知乎 <- 文中大部分内容来自于此
https://www.zhihu.com/question/419394566/answer/3191754366