概述
今天来跟大家分享一下cpu缓存相关的东西,在了解cpu缓存的工作原理时,举一反三,以后在学习一些缓存技术的实现的时候就会更加容易一些,现在那么多缓存技术,原理大多都大同小异。
基本描述
我们都知道,CPU运算速度远大于内存读写速度,这样会使CPU花费很长时间等待数据到来或把数据写入内存。在计算机系统中,CPU高速缓存(以下简称缓存)是用于减少处理器访问内存所需平均时间的部件。在金字塔式存储体系中它位于自顶向下的第二层,仅次于CPU寄存器。其容量远小于内存,但速度却可以接近处理器的频率。数据对于计算机芯片来讲就是有无电,读一个数据就取一个电荷(电荷就没了),因此需要按照某种频率不断刷新来进行充电,因为cpu缓存使用的是静态ram技术,不需要不断进行刷新,读一次可以继续重复读,所以很快,但是其电路元器件比RAM复杂的多。
当处理器发出内存访问请求时,会先查看缓存内是否有请求数据。如果存在(命中),则不经访问内存直接返回该数据;如果不存在(失效),则要先把内存中的相应数据载入缓存,再将其返回处理器。cpu将ram热区中的数据保存至缓存中,当cpu访问缓存中的数据,若缓存空间已满,则会使用某种置换策略将缓存中的数据置换出去。例如lru算法(最近最少使用或者最久未使用)和mru算法(最近最常使用算法,依赖于额外的标记位才能完成缓存置换,因此比lru的电路更复杂一些).
缓存之所以有效,主要是因为程序运行时对内存的访问呈现局部性特征(即在一段时间内,整个程序的执行仅限于程序中的某一部分。相应地,执行所访问的存储空间也局限于某个内存区域)。这种局部性既包括空间局部性(指一旦程序访问了某个存储单元,则不久之后。其附近的存储单元也将被访问),也包括时间局部性(指如果程序中的某条指令一旦执行,则不久之后该指令可能再次被执行,通俗易懂点就是如果某数据被访问,则不久之后该数据可能再次被访问。)。有效利用这种局部性,缓存可以达到极高的命中率。
缓存的存储结构
结构上,一个直接映射缓存由若干缓存块构成。每个缓存块存储具有连续内存地址的若干个存储单元。每个缓存块有一个索引,它一般是内存地址的低端部分,但不含块内偏移和字节偏移所占的最低若干位。由于这是一种多对一映射,必须在存储一段数据的同时标示出这些数据在内存中的确切位置。所以每个缓存块都配有一个标签。拼接标签值和此缓存块的索引,即可求得缓存块的内存地址。如果再加上块内偏移,就能得出任意一块数据的对应内存地址。
运作流程
下面简要描述一个假想的直接映射缓存的工作流程。这个缓存共有四个缓存块,每个块16字节,即4个字,因此共有64字节存储空间。使用回写策略以保证数据一致性。
系统启动时,缓存内没有任何数据。之后,数据逐渐被载入或换出缓存。假设在此后某一时间点,缓存和内存布局如右图所示。此时,若处理器执行数据读取指令,控制逻辑依如下流程:
将地址由高至低划分为四个部分:标签、索引、块内偏移、字节偏移。其中块内偏移和字节偏移各占两位,后者在以下操作中不使用。
用索引定位到相应的缓存块。
用标签尝试匹配该缓存块的对应标签值。如果存在这样的匹配,称为命中(Hit);否则称为未命中(Miss)。
如命中,用块内偏移将已定位缓存块内的特定数据段取出,送回处理器。
如未命中,先用此块地址(标签+索引)从内存读取数据并载入到当前缓存块,再用块内偏移将位于此块内的特定数据单元取出,送回处理器。这里要注意的是,(1)读入的数据会冲掉之前的内容。为保证数据一致性,必须先将数据块内的现有内容写回内存。(2)尽管处理器请求的只是一个字,缓存仍必须在读取的时候把整个数据块都填充满。(3)缓存的读取是按缓存块大小为边界对齐的。对于大小为16字节的缓存块,任何因为0x0000、或0x0001、或0x0002、或0x0003造成的未命中,都会导致位于内存0x0000—0x0003的全部四个字被读入块中。
在上图中,如此时处理器请求的地址在0x0020到0x0023之间,或在0x0004到0x0007之间,或在0x0528到0x052B之间,或在0x05EC到0x05EF之间,均会命中。其余地址则全部未命中。
而处理器执行数据写入指令时,控制逻辑依如下流程:
1:用索引定位到相应的缓存块。
2:用标签尝试匹配该缓存块的对应标签值。其结果为命中或未命中。
3:如命中,用块内偏移定位此块内的目标字。然后直接改写这个字。
4:如未命中,依系统设计不同可有两种处理策略,分别称为按写分配和不按写分配。如果是按写分配,则先如处理读未命中一样,将未命中数据读入缓存,然后再将数据写到被读入的字单元。如果是不按写分配,则直接将数据写回内存。
对于直接映射方式,为了便于数据查找,一般规定内存数据只能置于缓存的特定区域。对于直接映射缓存,每一个内存块地址都可通过模运算对应到一个唯一缓存块上。注意这是一种多对一映射:多个内存块地址须共享一个缓存区域。通过上图描述其工作过程,我们发现直接匹配缓存尽管在电路逻辑上十分简单,但是存在显著的冲突问题。由于多个不同的内存块仅共享一个缓存块,一旦发生缓存失效就必须将缓存块的当前内容清除出去。这种做法不但因为频繁的更换缓存内容造成了大量延迟,而且未能有效利用程序运行期所具有的时间局部性。从而出现了一种组关联技术。
组关联
使用组相联的缓存把存储空间组织成多个组,每个组有若干数据块。通过建立内存数据和组索引的对应关系,一个内存块可以被载入到对应组内的任一数据块上。
以下图为例, 如使用2路组相联,内存地址为0、8、16、24的数据均可被置于缓存第0组中两个数据块的任意一个;如果使用4路组相联,内存地址为0、8、16、24的数据均可被置于缓存第0组中四个数据块的任意一个。
当使用组相联时,在通过索引定位到对应组之后,必须进一步地与所有缓存块的标签值进行匹配,以确定查找是否命中。这在一定程度上增加了电路复杂性,因此会导致查找速度有所降低。
此外,在不增大缓存大小的前提下单纯地增加组相联的路数,将不会改变缓存和内存的对应比例。再以右图为例,对于2路组相联,尽管第0组内有两个缓存块,但是该组现在也是内存块1、9、17、25的目标块。
直接匹配可以被认为是单路组相联。经验规则表明,在缓存小于128KB时,欲达到相同失效率,一个双路组相联缓存仅需相当于直接匹配缓存一半的存储空间。
1路:ram的某几个存储单元只能缓存在cpu缓存的某一个存储单元
2路:ram的某几个存储单元只能缓存在cpu缓存的某两个存储单元
4路:ram的某几个存储单元只能缓存在cpu缓存的某四个存储单元
另外还有一种叫全相联的技术,全相联的一个极端是组相联。这种缓存意味着内存中的数据块可以被放置到缓存的任意区域。这种相联完全免去了索引的使用,而直接通过在整个缓存空间上匹配标签进行查找。 由于这样的查找造成的电路延迟最长,因此仅在特殊场合,如缓存极小时,才会使用。
下面我们再来说一下上文提到过的置换策略和回写策略:
对于组相联缓存,当一个组的全部缓存块都被占满后,如果再次发生缓存失效,就必须选择一个缓存块来替换掉。存在多种策略决定哪个块被替换。
先进先出算法(FIFO)替换掉进入组内时间最长的缓存块。
最久未使用算法(LRU)则跟踪各个缓存块的使用状况,并根据统计比较出哪个块已经最长时间未被访问。对于2路以上相联,这个算法的时间代价会非常高。
对最久未使用算法的一个近似是非最近使用(NMRU)。这个算法仅记录哪一个缓存块是最近被使用的。在替换时,会随机替换掉任何一个其他的块。故称非最近使用。相比于LRU,这种算法仅需硬件为每一个缓存块增加一个使用位即可。
此外,也可使用纯粹的随机替换法。测试表明完全随机替换的性能近似于LRU
为了和下级存储(如内存)保持数据一致性,就必须把数据更新适时更新下去。这种更新通过回写来完成。一般有两种回写策略:写回和写通。
写回是指,仅当一个缓存块需要被替换回内存时,才将其内容写入内存。如果缓存命中,则总是不用更新内存。为了减少内存写操作,缓存块通常还设有一个脏位(现代的内存管理单元是以页的方式,dirty bit表示该页是否被写过),用以标识该块在被载入之后是否发生过更新。如果一个缓存块在被置换回内存之前从未被写入过,则可以免去回写操作。
写回的优点是节省了大量的写操作。这主要是因为,对一个数据块内不同单元的更新仅需一次写操作即可完成。
写通是指,每当缓存接收到写数据指令,都直接将数据写回到内存。如果此数据地址也在缓存中,则必须同时更新缓存。由于这种设计会引发造成大量写内存操作,有必要设置一个缓冲来减少硬件冲突。这个缓冲称作写缓冲器,通常不超过4个缓存块大小。不过,出于同样的目的,写缓冲器也可以用于写回型缓存。写通较写回易于实现,并且能更简单地维持数据一致性。
当发生写失效时,缓存可有两种处理策略,分别称为按写分配和不按写分配。
按写分配是指,先如处理读失效一样,将所需数据读入缓存,然后再将数据写到被读入的单元。不按写分配则总是直接将数据写回内存。
设计缓存时可以使用回写策略和分配策略的任意组合。对于不同组合,发生数据写操作时的行为也有所不同。如下表所示。
回写策略 | 分配策略 | 当…时 | 写到… |
---|---|---|---|
写回 | 分配 | 命中 | 缓存 |
写回 | 分配 | 失效 | 缓存 |
写回 | 非分配 | 命中 | 缓存 |
写回 | 非分配 | 失效 | 内存 |
写通 | 分配 | 命中 | 缓存和内存 |
写通 | 分配 | 失效 | 缓存和内存 |
写通 | 非分配 | 命中 | 缓存和内存 |
写通 | 非分配 | 失效 | 内存 |
分享就到此结束,其实cpu缓存相关的东西不止于此,太深了
转载于:https://blog.51cto.com/xiaojielinux/1875073