一、引子
在这一小节中,我们要解决 cache 部分的最后一个内容,就是 cache 的写策略
。
之前我们提出了这样 3 个待解决的问题。
前两个问题我们已经解决了,还剩最后一个问题。 就是cache 当中保存的只是主存里的数据的一个副本, CPU 对 cache 里的数据进行写操作,修改了里边的数据之后,如何保持主存和 cache 的数据一致性。
这就是 cache 的写策略要探讨的问题。
我们会分为两种情况来探讨写的策略。
①第一种情况,如果此时 CPU 要写的存储单元被命中,也就是已经存在 cache 里边的话,这种情况如何处理?
我们可以有两种方法,一种叫全写法,一种叫写回法。
②第二种情况,如果此时 CPU 它想要写的地址没有命中。
我们又会有两种处理的策略,分别是写分配法和非写分配法。
🤔为什么我们在这儿只探讨写操作呢?
这是因为我们要解决的是 cache 和主存数据一致性的问题。如果CPU进行的是读操作,而不是写操作,无论是读命中还是读不命中,无论发生哪种情况,只要是读操作,就一定不会导致数据不一致的问题。
所以在这个小节中,我们只会探讨 CPU进行写操作的时候有可能发生的各种情况。
二、写命中
首先来看第一种情况。
就是 CPU要对某一个地址进行写,并且这个地址所对应的主存块已经被调入 cache 当中,发生了命中的情况。
在这种情况下,我们会有两种处理的策略。
(1)写回法
1.过程
第一种策略叫做写回法
。
我们结合之前的小结给出的具体的例子,假设此时 0 号主存块(绿色)已经被调入到了Cache绿色这个地方,紫色的主存块也被调入 cache 当中。
假设此时CPU 要进行写操作的地址刚好于 0 号,也就是绿色主存块它所对应的地址范围。
如果采用写回法,就意味着 CPU只会往 cache 当中的数据副本这儿写入相应的数据。
这个时候主存的数据母本和 cache 里的数据副本就发生了不一致的情况。
但是这种不一致,我们只有在第三个 cache 行被淘汰的时候,我们才会把整个 cache 行里存储的这一整块的数据,全部统一写回主存。
2.优化
对于紫色这一块数据,如果整个过程它都没有被修改过的话,当 cache 块被替换的时候,我们不需要把 cache 块写回主存,这样我们就可以节省一些写回的时间。
所以,为了让硬件能够区分哪个 cache 行的数据被修改过,哪一个没有被修改过,我们还需要给每一个 cache 行增加一个所谓的脏位
的信息。
当一个数据块被修改之后,我们需要把这个 cache 行所对应的脏位设为1。
这样的话,当我们淘汰某一个 cache 行的时候,就可以知道这个 cache 行是否需要写回主存。
而到底应该写回主存的什么位置,我们又可以根据标记来判断。
所以采用写回法可以使 CPU 的访存次数减少,从而节省写操作所需要的时间。
但是这种方式又存在数据不一致的隐患。
(2)全写法
1.过程
接下来看第二种方法,叫做全写法
,又叫写直通法。
如果采用这种方法,就意味着当 CPU 对 cache 写命中的时候,它除了会把数据写往 cache 之外,同时CPU也要往主存对应的存储单元写入相应的数据。
采用这种方法,就可以保证 cache 和主存的数据基本上都能够保持一致。
另外,当 cache 里的数据被淘汰的时候,我们也不需要像之前那样写回主存。因为这两边的数据随时都是保持一致的。
采用这种方法,就意味着 CPU 每一次进行写操作的时候,除了访问 cache 之外,也需要进行访存。
访存次数增加就会导致 CPU 写操作的速度变慢。
2.优化
为了减少 CPU 访存的次数,我们通常可以采用这样的优化策略。
我们可以增加一个所谓的写缓冲
。
写缓冲也是用 SRAM 来制造的,用 SRAM 制造就意味着对写缓冲的读和写操作会比较快。
我们可以把它看作是一个先进先出的队列。
来看一下如何使用。
<1> 当 CPU 对某一个地址进行写操作,并且这个地址命中的时候, CPU 首先会向 cache 当中写入数据。另外 CPU 也会往写缓冲里写入相应的数据。
<2> 接下来假设CPU又往紫色的写入了数据,同样的也会把这一块的内容写到写缓冲里。
由于写缓冲是用 SRAM 实现的,所以CPU 对写缓冲的写操作要比直接往主存里边写要快得多。
<3> 接下来 CPU 就可以去干其他的事情,比如会进行连续的好几次读操作。
当 CPU 干其他事情的期间,又会有一个专门的控制电路来负责把写缓冲里面写入的这些数据把它同步到主存里边。
同步后:
同样的,如果采用这种方式,当我们淘汰某一个 cache 行的时候,也不需要把 cache 行的数据写回主存。
增加了写缓冲之后,可以使得 CPU 的写操作写的速度会变得很快。
如果 CPU 的写操作不频繁,采用这种策略效果会很好。而如果 CPU 的写操作很频繁,由于我们写缓冲它的容量是有限的,所以如果写操作很多,就会导致写缓冲饱和。当写缓冲饱和之后,CPU 就必须阻塞等待,等它有空位再继续往里边写。
这就是全写法。
三、写不命中
到目前为止,我们探讨的是当CPU想要写的地址可以命中的情况下,我们可以采取的两种策略。
接下来我们再来看当CPU要写的地址不命中的情况下,可以采取的策略。
(1)写分配法
第一种处理的方法叫做写分配法
。
当 CPU此时要访问的地址没有命中,如果采用的是写分配法,我们就会先把CPU当前要写的这一块的数据,先把它调到 cache 当中。
然后CPU再对 cache 进行写操作,也就是说,主存里的这一块数据保持不动, CPU 只是修改了它的数据副本。
显然,这种方式比较适合和我们之前提到的写回法配合着使用。
也就是当 cache 块被淘汰的时候,才把这一整块的内容同步回主存里边。
这是写分配法,通常和写回法配合着使用。
(2)非写分配法
再看第二种处理方式,叫做非写分配法
。
指当 CPU 想要写的地址没有命中的时候,此时 CPU 会直接往主存里边写数据,而不会把这一块的内容调入cache。
所以非写分配法一般来会搭配着之前提到的全写法来使用。
如果采用这种方法,就意味着只有 CPU 对某一个地址进行读操作,读操作未命中的情况下,才会把相应的主存块调入cache。
而如果是写操作未命中, CPU 会直接写,而不会把这块调入cache。
这就是写操作不命中的情况下,可以采取的两种策略,一种叫写分配法,一种叫非写分配法。
四、多级Cache
现在我们使用的计算机通常会采用多级 cache 的结构。
最接近 CPU 的这一级 cache 编号是L1,往下一级是L2,也有一些 CPU 会有L3,三级cache。
越接近CPU 的速度会越快,但是容量会越小,因为成本会越高。而越远离CPO,它的速度越慢,容量也会越大。
比如我们之前给出的截图,可以看到I5 9300 H ,这个CPU它有三级 cache,L1, L2, L3 。最靠近 CPU 的 L1这一级的cache,它的读写速度几乎可以到 1000 GB 这样的一个量级。而第二级的cache,它的读写速度差不多比 L1 会慢一半。
对了,再补充一下,我们之前说过, cache 里边保存的是主存里的某一些数据,一小部分数据的副本。更高级更快速的cache,它所保存的数据又是更低一级 cache 的一小部分数据的副本。
因此,在各级 cache 之间同样存在数据一致性的问题。既然是要保证数据的一致性,因此就可以使用我们之前介绍的两种方法。
各级 cache 之间采用的是全写法加上非写分配法。而 cache 和主存之间通常会使用写回法加上写分配法,用这样的方式来保证数据的一致性。
这个部分内容做一个简要的了解即可。
大家也可以去自己的 windows 电脑的任务管理器去看一下性能页签。(下面是我的电脑的性能)
在这个地方你可以看到自己的CPU 的一个详细的信息,它有几级的cache,每一级的 cache 容量是多少。
这些信息大家可以去看一下。
五、总结回顾
这一小节中我们介绍了 cache 写策略,从而解决了 cache 与主存之间数据一致性的问题。
当 CPU 要写的地址可以命中的情况下,可以采取全写法和写回法这两种策略,而当写不命中的时候,可以采取写分配法和非写分配法这样的两种处理方式,通常写分配法和写回法会配合着使用,而非写分配法会和全写法配合着使用。
在这个小节的最后,我们也简单地介绍了多级cache。
本质上,各级 cache 的作用和 cache 与主存之间的作用其实都是一样的,都是为了尽可能的降低成本,但是同时又尽可能地提升 CPU 的运行速度。
各级 cache 之间的数据同步通常会采用全写法配合非写分配法这样的方式来解决。而 cache 和主存之间通常采用写回法和写分配法来解决数据一致性的问题。
好的,到目前为止,我们就解决了在 cache 的第一个小节当中提出的这 3 个问题。
①第一个问题,如何区分 cache 和主存它们之间数据块的对应关系?这就是 cache 和主存的映射方式所要探讨的问题。分为全相联映射、直接映射和组相联映射。
②在上个小节当中,我们又解决了当 cache 满了之后,我们应该怎么办这样的问题。我们介绍了 4 种替换算法,其中最常用的应该是LRU,也就是最近最久没使用算法。
详情戳:3.9.3Cache替换算法
③而这个小结中,我们又解决了第三个问题,就是 cache 和主存当中数据一致性的问题。
以上就是关于 cash 的相关考点。