第一句子网 - 唯美句子、句子迷、好句子大全
第一句子网 > Cache(三):cache的常见名词与Cache一致性问题简介

Cache(三):cache的常见名词与Cache一致性问题简介

时间:2023-03-07 13:14:29

相关推荐

Cache(三):cache的常见名词与Cache一致性问题简介

这里一起学习一点关于学习cache中常见的术语,并再次复习一下。

学习的前辈的blog链接:/vivo01/article/details/127243849

1、关于D(Dirty)bit

当采用写回策略时,CPU写数据时,如果cache命中,则只会更新cache里的内容,此时内存中的数据和cache中的数据不一致。这种情况下对应cache line的D bit被置位。那么什么时候才将cache的内容同步到内存上呢?通常有两种情况:1. 当这个cache line需要被替换出去(evict, 踢出)的时候,如果D bit有效,则cache的数据修改会回写到内存(严谨一点是下一级存储)上;当CPU主动执行clean或flush操作时,cache line的内容会回写到内存。

2、VIVT,VIPT,PIPT,PIVT都是些啥?

有多种不同的cache地址组织方式,比如VIVT,VIPT,PIPT,PIVT。看到这些词是不是心里面想问候一下亲人了。其实最常见的也就是VIPT,PIPT也能见到。

首先我们拆开字母分别来看,V代表Virtual,表示虚拟地址;P代表Physical,表示物理地址;I表示index;T表示tag。以VIPT为例,表示用虚拟地址对应bits来取index值,物理地址对应bits作为tag的值。后面提到的VA指Virtual Address,PA指Physical Address。

1、VIVT

这种组织方式的cache,index和tag全部使用虚拟地址。

VIVT方式的cache,在CPU访问某个虚拟地址时,主要过程如下:首先根据虚拟地址得到index,通过index找到cache line(这里不纠结于是直接映射还是组相连之类的结构),然后对比tag(同样为虚拟地址)。如果cache hit,则直接从cache中取数据,如果cache miss,则将VA送至MMU转换为PA后访问主存,将对应数据放到cache line中并返回给CPU。

可以看到,如果使用VIVT进行cache访问,不需要每次读写都经过MMU将VA转成PA后再去查表,一定程度上降低了访问延迟。但VIVT也引入了两个问题:歧义(Ambiguity)和别名(Alias)。

2、歧义(Ambiguity)

歧义这个词,从字面上理解上就是词语本身会产生两种或两种以上可能的意思,容易让人误解。我们将“词语”换成虚拟地址,“可能的意思”换成cache缓存的物理地址的内容,那么VIVT里引入的歧义的意思就是,同一个虚拟地址,对应的cache line中缓存的内容对应到了不同的物理地址上。

上面这样说可能还是抽象了点,举个简单的例子就比较直观了。假设有两个进程A和B,它们都要访问虚拟地址0x1000, 进程A中VA 0x1000对应的是PA 0x8000;进程B中VA 0x1000对应PA是0x9000。假设进程A先调度,访问了0x1000,那么此时对应cache line中存的数据来自PA 0x8000地址;然后进程B调度后,同样去访问0x1000。由于VIVT方式都是使用VA来取index和tag,因此如果没有任何特殊处理,对cache来说,由于index和tag一样,必然会cache命中,cache就会返回PA 0x8000的内容。为了避免歧义问题,操作系统在进行进程间切换时,一般会做cache flush操作。

做cache flush是需要时间的,因此我们可以知道,VIVT的cache,频繁切换进程的过程中会有性能损失。这种损失有两方面,一方面是flush操作本身,另一方面就是切换到下一个进程后,这个进程所有的内存访问都会发生cache miss。

3、别名(Alias)

现实生活中,别名很好理解,比如你的外号就算是一个别名。VIVT中的别名问题,这个名字我们理解为虚拟地址,实际指代的“人”我们理解成物理地址就可以了。别名的意思就是一个PA有两个或多个VA相对应。

同样,我们来看一个例子,假设两个虚拟地址VA:0x1060和0x8030,这两个虚拟地址都对应同一个PA 0x9000。由于VIVT使用VA作为tag和index,对于这两个VA来说,就使用了两个cache line。这两个cache line存的是同一个PA的内容。如果只是读操作,问题还不大。但如果是写操作,并且cache的写策略是write back,那么问题就来了。假设程序A先对这两个VA进行了一次读操作,cache分配了两条cache line存储了相同的内容。假设程序接下来对VA 0x1060进行了写操作,由于是write back,这个数据不会立即写到PA对应的内存上。接下来程序要对0x8030进行访问,如果是读操作,则cache命中,返回的值还是原始读到的值而不是更新后的值;如果是写操作,那么这两个cache line里的内容也不一样了。

为了解决这个问题,对于不同的进程而言,进程切换时做flush cache能够解决问题。如果是同一个进程内要共享内存,通过nocache方式做映射,这样共享内存就不经过cache了,但大家也能看出,nocache方式性能损失肯定是巨大的。

4、PIPT

这种方式下,cache的tag和index都是按照PA来索引的,因此不存在歧义和别名问题。其逻辑结构如下图:

首先,VA经过MMU转换成PA,然后根据PA找到index和tag。这种方式虽然能解决歧义和别名问题(index和tag都是唯一的)。对于软件来说,PIPT基本做到了cache透明化。但是对硬件来讲,这种方案也有两个明显的不足:1. 每次进行cache访问时,VA必须经过MMU转换为PA,这个步骤相比于VIVT会较为繁琐。2. 硬件实现上比VIVT要复杂,成本也更高。

5、VIPT

VIPT的逻辑结构如下图:

VIPT方式下,使用虚拟地址的index来查找cache line,同时VA会发送到MMU转换成PA,然后通过PA取出tag去对比tag entry的值。这种方式综合了VIVT和PIPT,那么这种方式是否存在歧义和别名问题呢?

先看歧义问题,本质是因为同一个虚拟地址对应到了不同的物理地址。还是以之前的两个进程A,B为例,进程A访问0x1000对应PA是0x8000;进程B访问0x1000对应PA是0x9000。由于VIPT使用PA作为tag,因此,不会出现0x1000在不同进程中访问到了同一个cache line数据的问题。因此VIPT不存在歧义问题。再来看别名问题,VIPT是否有别名问题会稍微复杂一点。首先来看不会出现别名问题的场景。一般情况下,Linux的内存管理的最小单位是页,对于4KB的页大小。如果cache的大小也等于4KB,那么此时bits[11:0]作为页内偏移,实际上整体是作为转换后的PA的低12 bit偏移作用。这种情况下index本身也属于PA的一部分。这样无论使用VA查index还是PA查index,效果是一样的,这种情况和PIPT基本等价,因此也就不会有别名问题产生。如果是多路组相连cache,只要cache的大小除以way的数量的值,小于等于页的大小,也不会存在别名问题。相对的,假设仍然是4KB的页,但是cache容量为8KB(对于多路组相连cache,一路大小为8KB)。那么别名问题就会出现了。这里假设cache line大小为256 bytes。因此cache line总共有32个,需要5 bits作为index,offset需要8 bits。因此index + tag总共占13个bits。对于4KB的页,VA和PA相同的bit有12个,因此index里多出了1 bit虚拟地址里的值。假设VA 0x50000和 0x51000都映射到了同一个PA 0x80000, 那么这个PA对应的cache line就会有多个,别名问题就出现了。

6、PIVT

如果说VIPT是缝合地不错的方案,它结合了PIPT和VIVT的优点。那么PIVT缝合出来的就是怪物了,没有保留到PIPT和VIVT的优点,还将两者的缺点都全收了。这种方案可以说基本不会有厂家用(网上搜索说MIPS R6000采用这种方式,很快被淘汰,没有求证过)。这里就不详细说了,感兴趣的朋友们可以自己去画图看看。

3、Cache一致性问题简介

虽然使用cache带来了很大的性能提升,但它也有副作用。最常见的一个问题就是cache一致性问题。本笔记会简单介绍cache一致性问题发生的原因。

先来看一个简单的双核心,3级缓存的结构:

如上图所示,Core0和Core1有分开的L1指令和数据cache和一个L2缓存。所有Core共享一个L3缓存。

cache的一致性问题,可以从三个范围来看:

1. 单个core内指令和数据cache的一致性

对于有些自修改的程序而言,比如我们修改了一个地址上的指令A,这是通过修改数据存储的操作来实现的,因此修改的是数据cache中的内容。接下来我们要读取指令A并运行,这时走的是指令cache,如果没有考虑一致性。可能出现指令cache命中,但cache的数据不是修改后的数据,导致读到旧指令的问题。解决这种问题的方法是做完数据cache对指令A的修改后,对指令cache做无效化操作,这样当再次访问指令A时会重新从内存中加载回来。

2. 多个core之间L2 cache的一致性

从图中可以看到,两个core的L2 cache共享一个L3 cache。假设Core 0访问地址A,此时Core 0 的L1,L2 cahe,以及L3 cache中就有了地址A对应的数据。如果Core 1也要访问地址A,这时,cache的硬件能够直接把Core 0中对应地址A的数据直接复制到Core 1的L1,L2 cach中。当Core 0和Core 1都缓存了地址A的内容后,此时如果Core 0对地址A进行了修改,更新到了Core 0的L1 cache中,如果没有一定的机制处理这个情况,当Core 1访问地址A时,就会拿到旧数据。因此需要实现这种同步的机制,这就是cache一致性协议所要解决的问题。

3. L3 cache和外设内存(比如PCIE设备,网络设备等具有DMA功能的外设)的一致性

写驱动的小伙伴对这个事情一定不陌生,如果外设要通过DMA读写主存,那么这段时间内CPU需要做相应的处理,比如flush cache或invalidate cache,具体和数据的流向相关。这部分内容,网上有很多资料,由于和本笔记关注的点不太吻合,因此不详细展开。为了解决cache一致性问题,引入了cache一致性协议。常见的cache一致性协议类型有两种:snooping 和 directory 类型的一致性协议。如果想要详细了解cache一致性协议的实现细节,可以参考我之前翻译过的《A primer on memory consistency and cache coherence》这本书,这本书的6-8章详细讲解了cache一致性协议相关内容。

(翻译的链接:/vivo01/article/details/123234273?spm=1001..3001.5501)真牛

参考资料:

/m0_46792634/article/details/124626640(还可以看一下这篇)

4、问题

问题:A77增大了LSU的load和store buffer,同时可以支持85级深度load 操作和90级深度store操作。这个多少级的深度指的是什么?

还有架构的后端的发射队列,这个是指的什么?

答案:深度是指与mem交互缓存的大小,一般情况下访问mem时延越大,buffer深度越大,与外部交流的outstanding能力强。

后端发射队列一般是指令经过译码,重命名后待执行的指令,缓存到发射队列,等到哪个执行单元释放后再从队列拿出来执行。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。