[转]甄建勇:五分钟搞定Cache(下)
2021-11-20 08:00:00 Author: blog.csdn.net(查看原文) 阅读量:24 收藏

作者简介

甄建勇,高级架构师(某国际大厂),十年以上半导体从业经验。主要研究领域:CPU/GPU/NPU架构与微架构设计。感兴趣领域:经济学、心理学、哲学。 

f927c544eaea461f0f69fa30fa5c62fa.png

关于Cache的其它内容

上面我们所描述情况,在访问cache前,已经将虚拟地址转换成了物理地址,其实,不一定,也可是是虚拟地址直接访问cache,倒底是使用物理地址还是虚拟地址,这就是翻译方式的选择。

(1)虚缓存

一个简单的方案就是缓存的标签和索引均使用虚拟地址。这种缓存称为虚缓存(virtualcache)。这种缓存的优点是仅在缓存失效时才需要进行页面翻译。由于缓存命中率很高,需要翻译的次数也相对较少。

但是这种技术也存在严重的问题。

第一,引入虚拟地址的一个重要原因是在软件(操作系统)级进行页面保护,以防止进程间相互侵犯地址空间。由于这种保护是通过页表和翻译旁视缓冲器(TLB)中的保护位(protectionbit)实现的,直接使用虚拟地址来访问数据等于绕过了页面保护。一个解决办法是在缓存失效时查看TLB对应表项的保护位以确定是否可以加载缺失的数据。

第二,由于不同进程使用相同的虚拟地址空间,在切换进程后会出现整个缓存都不再对应新进程的有效数据。如果前后两个进程使用了相同的地址区间,就可能会造成缓存命中,确访问了错误的地址,导致程序错误。有两个解决办法:其一,进程切换后清空缓存。代价过高。其二,使用进程标识符(PID)作为缓存标签的一部分,以区分不同进程的地址空间。

第三,别名问题(Alias)。由于操作系统可能允许页面别名,即多个虚拟页面映射至同一物理页面,使用虚拟地址做标签将可能导致一份数据在缓存中出现多份拷贝的情形。这种情况下如果对其中一份拷贝作出修改,而其他拷贝没有同步更新,则数据丧失整合性,导致程序错误。有两个解决办法:其一,硬件级反别名。当缓存载入目标数据时,确认缓存内没有缓存块的标签是此地址的别名。如果有则不载入,而直接返回别名缓存块内的数据。其二,页面着色(PageColoring)。这种技术是由操作系统对页面别名作出限制,使指向同一页面的别名页面具有相同的低端地址。这样,只要缓存的索引范围足够小,就能保证在缓存中决不会出现来自不同别名页面的数据。

第四,输入输出问题。由于输入输出系统通常只使用物理地址,虚缓存必须引入一种逆映射技术来实现虚拟地址到物理地址的转换。 

(2)实缓存

实缓存(physicalcache)完全使用物理地址做缓存块的标签和索引,故地址翻译必须在访问缓存之前进行。这种传统方法所以可行的一个重要原因是TLB的访问周期非常短(因为本质上TLB也是一个缓存),因而可以被纳入流水线。

但是,由于地址翻译发生在缓存访问之前,会比虚缓存更加频繁地造成TLB。(相比之下,虚缓存仅在本身失效的前提下才会访问TLB,进而有可能引发TLB失效)实缓存在运行中存在这样一种可能:首先触发了一个TLB失效,然后从页表中更换TLB表项(假定页表中能找到)。然后再重新访问TLB,翻译地址,最后发现数据不在缓存中。

(3)虚索引、实标签缓存

一个折中方案是同时使用虚索引和实标签(virtuallyindexed, physically tagged)。这种缓存利用了页面技术的一个特征,即虚拟地址和物理地址享有相同的页内偏移值(pageoffset)。这样,可以使用页内偏移作为缓存索引,同时使用物理页面号作为标签。这种混合方式的好处在于,其既能有效消除诸如别名引用等纯虚缓存的固有问题,又可以通过对TLB和缓存的并行访问来缩短流水线延迟。

这种技术的一个缺点是,在使用直接匹配缓存的前提下,缓存大小不能超过页面大小,否则页面偏移范围就不足以覆盖缓存索引范围。这个弊端可以通过提高组相联路数来改善。

(4)多级cache

介于处理器和内存二者之间的缓存有两个天然冲突的性能指标:速度和容量。如果只向处理器看齐而追求速度,则必然要靠减少容量来换取访问时间;如果只向内存看齐而追求容量,则必然以增加处理器的访问时间为牺牲。这种矛盾促使人们考虑使用多级缓存。

在一个两级缓存体系中,一级缓存靠近处理器一侧,二级缓存靠近内存一侧。当一级缓存发生失效时,它向二级缓存发出请求。如果请求在二级缓存上命中,则数据交还给一级缓存;如失效,二级缓存进一步向内存发出请求。对于三级缓存可依此类推。

通常,更接近内存的缓存有着更大容量,但是速度也更慢。

值得注意的是,无论如何,低级缓存的局部命中率总是低于高级缓存。这是因为数据的时空局部性在一级缓存上基本上已经利用殆尽。

对于各级cache,虽然功能类似,但不同级别的缓存在设计和实现上也有不同之处。

一般而言,在存储体系结构中低级存储总是包含高级存储的全部数据,但对于多级缓存则未必。相反地,存在一种多级排他性(Multilevelexclusion)的设计。此种设计意指高级缓存中的内容和低级缓存的内容完全不相交。这样,如果一个高级缓存请求失效,并在次级缓存中命中的话,次级缓存会将命中数据和高级缓存中的一项进行交换,以保证排他性。

多级排他性的好处是在存储预算有限的前提下可以让低级缓存更多地存储数据。否则低级缓存的大量空间将不得不用于覆盖高级缓存中的数据,这无益于提高低级缓存的命中率。

当然,也可以如内存对缓存般,使用多级包容性(Multilevelinclusion)设计。这种设计的优点是比较容易方便查看缓存和内存间的数据一致性,因为仅检查最低一级缓存即可。对于多级排他性缓存这种检查必须在各级上分别进行。这种设计的一个主要缺点是,一旦低级缓存由于失效而被更新,就必须相应更新在高级缓存上所有对应的数据。因此,通常令各级缓存的缓存块大小一致,从而减少低级对高级的不必要更新。

此外,各级缓存的写策略也不相同。对于一个两级缓存系统,一级缓存可能会使用写通来简化实现,而二级缓存使用写回确保数据一致性。orpsoc正是这样设计的。 

(5)关于cache的优化

优化缓存可从三个方面入手:减少命中时间,降低失效率,减轻失效代价。此外,增加缓存访问带宽也能有效较低AMAT(平均内存访问时间,AverageMemory Access Time)。

理论上,完全使用虚拟地址可以获得更快的缓存访问速度,因为这样仅在缓存失效时才会进行地址翻译。但是,如前所述,这种纯虚地址缓存由于绕开了操作系统对进程访问地址的软件控制,会存在不少问题。

为了能接近虚缓存的访问速度,又能避开虚缓存带来的种种问题,引入了所谓虚索引、实标签缓存(virtuallyindexed, physically tagged)。这种结构的缓存可以令地址翻译和缓存查询并发进行,大大加快了缓存的访问速度。

由于电路延迟很大程度上取决于存储芯片的大小,所以可考虑使用较小容量的缓存以保证最短的访问周期。这么做的另一个好处是,由于一级缓存足够小,可以把二级缓存的全部或部分也集成到CPU芯片上,从而减少了二级缓存的命中时间。

AMD从K6到Opteron连续三代CPU的一级缓存容量都没有任何增长(均为64KB)正是基于这个原因。

另一方面,考虑使用简单的缓存,如直接匹配缓存,也可较组相联缓存减少命中时间。

路预测:

所谓路预测(Wayprediction),是指在组相联缓存中,跟踪同一组内不同缓存块的使用情况,然后在访问到来时,不经比较直接返回预测的缓存块。当然,标签比较仍然会进行,并且如果发现比较结果不同于预测结果,就会重新送出正确的缓存块。也就是说,错误预测会造成一个缓存块长度的延迟。

模拟表明路预测的准确率超过85%[6]。这种技术非常适合于投机执行(SpeculativeExecution)处理器,因为这种处理器有完善的机制来保证在投机失败之后取消已经派发的指令。

追踪缓存:

与一般的指令缓存存储静态连续地址不同,追踪缓存(TraceCache)存储的是基于执行历史的动态地址序列。这实际上是把分支预测的结果用在了缓存上。由于只存储沿某一特定分支路径才会遇到的指令,这种缓存可比传统缓存更节省空间。

追踪缓存的缺点是实现复杂,因为必须设法连续存储的数据并不会按照2的幂次字长对齐。此外,对于不同执行路径要分开存储。如果这些执行路径中存在相同地址的指令,这些指令就只好被分别存到两个地方。这反而造成了低效的空间利用。

Intel的Pentium4处理器使用了这一复杂技术。值得一提的是,Pentium4追踪缓存存储的不是从内存抓取的原始指令,而是已经过解码的微操作,从而进一步节省掉了指令解码上要花的时间。

增加访问带宽。

缓存流水线化:

将一级缓存并入流水线是一般做法。这种做法可行性在于一级缓存的访问时间通常都极短,可能只有一到数个CPU周期。此外,由于TLB也是一种高速缓存硬件,所以也可以纳入流水线。

非阻塞缓存:

一般而言,当缓存发生失效时,处理器必须停滞(stall),等待缓存将数据从次级存储中读取出来。

对于跨序执行(Out-of-orderExecution)处理器,由于多条指令在不同处理单元中并发执行,某一条指令引发的缓存失效应该只造成其所在处理单元的停滞,而不影响其他处理单元和指令派发单元继续流水。因此,有必要设计这样一种缓存,使之能够在处理缓存失效的同时,继续接受来自处理器的访问请求。这称为非阻塞缓存(Non-blockingcache)。

降低失效率。

使用更大的数据块:

使用大数据块有助于利用空间局部性降低失效率,但其代价是更高的失效代价。这是因为,一旦失效,就必须把整个数据块都重新填满。

使用更大的缓存:

单纯增大缓存的容量也是降低失效率的一个办法。不过显然这也增大了命中时间。

高组相联缓存:

使用多路组相联可以减少冲突失效。但其后果是缓存电路逻辑复杂化,故增大了命中时间。

编译器优化:

存在多种编译器优化技术来间接影响缓存的使用模式。 

End

精彩回顾

甄建勇:芯片架构方法学

甄建勇:五分钟搞定MMU

甄建勇:五分钟搞定Cache(上)

8536a510bca2405aca752a1b381b1b56.png

扫码加入社群

更多精彩尽在"Linux阅码场",扫描下方二维码关注

bf50886815e8121f2e0290a6fd8a6777.png

f6ed9741193e58bdd53376730954f1bf.gif

别忘了分享、点赞或者在看哦~


文章来源: https://blog.csdn.net/21cnbao/article/details/121448601
如有侵权请联系:admin#unsafe.sh