大而快:层次化存储(二)

1.写在前面

前面的博客我们介绍完了存储的一些简单的内容,同时的我们简单的入了个门的缓存。今天我们继续讲缓存。

2.cache

2.1cache访问

下面是对一个大小为8个数据块的空cache进行9次存储访问的操作序列,包括每次存储访问的具体操作。如下图,给出每次失效后cache中的内容变化。由于该 cache中有8个数据块,地址的低3位用来表示数据快号。

在这里插入图片描述

由于cache初始为空,许多地址的首次访问都为失效。

在这里插入图片描述

在这里插入图片描述

上面描述了每一次访存后的具体操作。在第8次访存后产生了数据块的地址冲突,地址18(10010)的数据字应放入cache的数据块2(010)中。因此,它需要将已经数据块2中的数据,也就是地址26(11010)的数据替换掉。这个策略使得cache可以有效利用时间局部性:使用最近访问的数据替换最近不常访问的数据。

这种情况类似于,从书架上取下一本所需书籍,但你的书桌上没有多余的地方可放,必须将一些已经在书桌上的书重新放回书架。在直接映射cache中,只有一个位置来存放最新访问的数据项,因此替换项也只有一个选择。

对于每一个可能的地址,都需要在cache中进行如下的查找:使用地址低位找到对应的唯一cache数据块。

在这里插入图片描述

访问地址被划分为:

  • 标签字段:用来和cache中存放数据的标签为进行比较。
  • 索引字段:用来选择数据块。

cache数据块的索引和标签唯一确定了应放于该块的数据字的存储地址。由于索引字段被用来作为访问cache的地址,而一个n位的二进制数有2^n个数值,因此直接映射cache的数据块总数应为2的幂。数据字的地址是4字节对齐的,每个地址的最低两位用来表示对应的字节地址。因此,如果存储都是字对齐的,那么在访问地址字时地址的最低两位可以忽略不计。

访问cache所需的所有数据,是cache容量和存储地址大小的函数。这是因为cache中既保存了数据,也保存了标签,其中,单个数据块的大小是4字节(单字),但是通常都会比它大。对于如下情况:

  • 64位地址。
  • 直接映射cache
  • cache大小为2^n数据块,因此索引字段为n位。
  • 数据块大小为2m个单字(2m+2),因此在单个数据块中使用m位索引单字,使用地址的最低2位来索引字节。

容量更大的块可以通过挖掘空间局部性来降低失效率。随着块大小的增长,失效率通常都在下降。如果单个块大小占cache容量的比例增加到一定程度,失效率最终会随之上升。这是因为cache中可存放的块数变少了。最终,某个数据块会在它的大量数据被访问之前就被挤出cache。另一方面,对于一个较大的数据块,块中各字之间的空间局部性也会随之降低,失效率降低带来的号处也就随之减少。

增大容量时,另一个更严重的问题就是失效损失。失效损失是从下一级存储获得数据块并加载到cache的时间。该时间分为两部分:访问命中的时间和数据的传输时间。很明显,除非我们改变存储系统,否则传输时间也将随着数据块容量的增大而增大。而且,随着数据块容量的增大,失效率改善带来的收益开始降低。最终的结果是,失效损失增大引起的性能下降超过了失效率降低带来的收益,cache性能当然随之下降,当然,如果能设计出高效的传输大数据块的存储,就可以增大数据块的容量,并得到更大的性能改善。

在这里插入图片描述

注意:对于大数据块很难解决失效损失中的长延迟问题,但可以隐藏部分传输时间来有效地减少失效损失。最早采用该思想的技术称为“提早重启”,即只要数据块中的所需数据返回来,就继续执行,无须等到该数据块中所有数据都完成传输。

另一种更为复杂的方案是,重新组织存储,让所需的数据首先从存储传输到cache中,再继续传输数据块中的剩余部分。从所需数据之后的地址,直到该数据块的开头。该技术被称为请求字先行或关键字先行。比提早重启技术性能稍好。

2.2处理cache失效

控制单元必须能够检测到失效,然后通过从内存中取得所需的数据来处理失效。如果cache命中,计算机将继续使用数据,就像什么都没有发生过一样。

当cache命中时,对处理器的控制逻辑进行修改并没有那么重要。不过,cache失效时,则需要一些额外的工作。cache的失效处理与两部分协同工作:一部分是处理器的控制单元;另一部分是单独的控制器,用来初始化内存访问和重填cache。cache的失效处理会引发流水线的停顿,这与例外或者中断处理不同,后者需要保存所有寄存器的状态。cache失效将会停顿整个处理器来等待内存,特别是冻结临时寄存器和程序员可见寄存器的内容。更为复杂的是,乱序执行的处理器在等待cache失效处理时允许继续执行指令。不过,本节中的按序处理器都假设在cache失效时停顿流水线。

进一步仔细考虑如何处理指令失效,相同的方法可以方便地扩展到处理数据失效。如果一条指令访问引发失效,那么指令寄存器的内容将被置为无效。为了将正确的指令写入cache中,必须能够对下一级存储发出读操作。由于程序计数器是在执行的第一个时钟周期递增,引发指令cache失效的指令地址就等于程序计数器的数值减4。一旦确定了地址,就需要指导主存进行读操作。等待内存响应(因为该访问将耗费多个时钟周期),然后将含有所需指令的(指令)字写入指令cache中。

一旦发生指令cache失效,可以定义如下处理步骤:

  1. 将PC的原始值(当前PC-4)发送到内存。
  2. 对主存进行读操作,等待主存完成本次访问。
  3. 写cache表项,将从内存获得的数据写入到该表项的数据部分,将地址的高位(来自于ALU)写入标签字段,并将有效位置为有效。
  4. 重启指令执行。这将会重新取值,本次取指将在指令cache中命中。

2.3处理写操作

写操作有一些不同。例如,对于存储指令,只把数据写入数据cache(不需要改变主存)。完成写入cache的操作后,主存中的数据将和cache中数据不同。在这种情况下,cache和主存称为不一致。保持cache和主存一致的最简单方法是,总是将数据写回内存和cache。这样的写策略称为写穿透或者写直达。

写操作的另一个关键点事写失效的处理。先从主存中取来对应数据块中的数据,之后将其写入cache中,覆盖引发失效的数据块中的数据。同时,也会使用完整地址将数据写回主存。

虽然上述设计方案能够简单地处理写操作,但是它的性能不佳。基于写穿透策略,每次的写操作都会引起写主存的操作。这些写操作延时很长,至少100个处理器时钟周期,这会大大降低处理器的性能。

解决这个问题的方法之一是使用写缓冲。写缓冲中保存这等待写回主存的数据。数据写入cache的同时也写入写缓冲中,之后处理器继续执行。当写入主存的操作完成后,写缓冲中的表项将被释放。如果写缓冲满了,处理器必须停顿流水线直到写缓冲中出现空闲表项。当然,如果主存写操作的速率小于处理器产生写操作的速率,多大容量的缓冲都无济于事。因为写操作的产生的速度远远快于主存系统的处理速度。

即使写操作的产生速度小于主存的处理速度,还是会产生停顿。通常,当写操作成簇发生时,会发生上述现象。为减少这样的停顿发生,处理器通常会增加写缓冲的表项数。

相对于写穿透或者写直达策略,另一种写策略称为写返回。基于写返回策略,当发生写操作时,新值只被写入cache中,被改写的数据块在替换出cache时才被写到下一级存储。写返回策略能够改善性能,尤其是当处理器写操作的产生速度等于或者大于主存的处理速度时。不过,写返回策略的实现比写穿透策略要复杂的多。

考虑写穿透cache中的写失效处理。最常见的策略是,在cache中为其分配一个数据块,称为写分配。将该数据块从内存取入cache中,改写该数据块中相应部分的数据。另一种策略则是,在内存中更新相应部分的数据,并不将其取入cache。这种策略称为写不分配。该策略的动机是,有时候程序需要写整个数据块。例如操作系统对某一页内存进行初始操作。在这样的情况下,初始写失效就将对应数据块取至cache里,这样的操作是不必要的。有些处理器允许以页为粒度来修改写分配策略。

事实上,在写返回cache中实现高效的写操作比在写穿透cache中要复杂得多。写穿透cache中,可以在比对标签的同时就将数据写入cache。如果标签比对不符,发生cache失效。由于cache是写穿透的,对应数据块的改写不会产生严重的后果,因为主存和cache中的数据都是正确的。但是,在写返回cache中,如果cache中的数据已被修改并产生cache失效,必须先将对应的数据块写回内存。如果在处理存储指令时,在不知道cache是否命中之前,只是简单地改写cache中对应的数据块,那么可能会破坏数据块的中的内容,这些内容还未及时备份到下一级存储中。

在写返回cache中,由于不能直接改写数据块,或者使用两个周期去处理存储指令,或者需要一个写缓冲来保存数据,通过流水化操作在一个周期内高效地处理存储指令。当使用存储缓冲器时,在正常的cache访问周期里,处理器进行cache查找,同时将数据放入到存储缓冲器中。如果cache命中,在下一个无用的cache访问周期里把新数据从存储缓冲器写入cache中。

相比之下,写穿透cache中写操作可以在一个周期内完成,读取标签的同时将数据写入对应的数据块中。如果写操作的数据块地址与标签匹配,处理器继续正常执行,因为更新的是正确的数据块。如果标签不匹配,处理器产生写失效,将该地址对应的数据块剩余内容取到cache中。

写返回cache也有写缓冲。当cache发生失效替换修改过的数据块时,写缓冲可以用来减少失效损失。在这种情况下,在从主存读取所需数据块时,修改过的数据块被放入cache的写返回缓冲,之后再由写返回缓冲写回主存中。如果下一个失效不会立即发生,当脏数据块被替换时,该技术可使损失减少一半。

2.4总结

本章从分析一个最简单的cache开始:直接映射,数据块容量为一个字。在这样的cache中,命中和失效都很简单,因为一个字正好一个数据块,每个字都有独立的标签。为了保持cache和主存数据的一致性,可以使用写穿透的策略,这样每次对cache的写操作都会引发主存的更新。相对写穿透,另一种策略是写返回,当数据块被替换时,才将其写入到主存中。

为利用空间局部性,cache的数据块容量应大于一个字。使用更大容量的数据块将会降低失效率,减少cache中与数据存储相关的标签的存储,从而提高cache的效率。虽然更大的数据块容易可以降低失效率,但也会增加失效损失。如果失效损失随着数据块容量呈线性增长,那么更大的数据块容量很容易导致更低的性能。

为避免性能损失,加大主存带宽来更高效地传输cache数据块。通常加大DRAM带宽的方法是加宽主存和交叉访问。DRAM设计者不断改善处理器和主存之间的接口,加大簇发传输模式下两者之间的带宽,减少大容量cache数据块的开销。

3.写在最后

本节主要介绍了缓存的使用,缓存失效的处理的策略,写缓存的时候如何去处理缓存的失效,下篇博客我们将介绍如何处理改进对应的缓存。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值