【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>
只要涉及到缓存的业务场景就一定会出现数据一致性问题。对于该问题,从微观的角度来看cpu与内存之间建立了N级缓存来提高效率,从宏观的角度来看分布式存储使用数据副本机制来提高数据的安全性和访问效率,不同的业务场景解决的技术手段不同,本文主要讨论cpu的缓存一致性问题。由于对cpu内部结构的详细情况不熟,本文只会对核心的概念进行讨论。
通过cpu-z工具就可以看到如下图所示的内容:
图中显示了cpu有3级缓存,其中一级缓存分为数据缓存和指令缓存其余的只有数据缓存(至于为什么只有一级缓存有指令缓存本人暂不清楚),每级的缓存大小也不同并且数据缓存级别越高空间越小。缓存行大小(即line-size)都是64byte,组关联(即way-set associative)数量也不同。现围绕这些内容进行讨论。
1、cpu在访问内存数据时需将数据先加载到缓存中,然后从缓存中读取,缓存行是内存与缓存进行数据交换的基本单位,在本人电脑中为64字节。
2、当cpu读取数据时如何知道在哪个cache line中呢(注意这里说的数据先当成1个字节算,而不要当作short、int、long等类型,后面会说明这个问题),有以下方法:
(1)将内存按照cache line 大小即64byte为一个数据块,对于4Gb的内存可以分为个数据块,对于每个cache line而言会额外记录当前访问数据所属数据块的地址(暂时命名为tag字段,长度为26bit)。遍历所有的cache line,然后将数据地址/64并与tag字段进行验证是否相等,如果相等表示当前数据在该cache line 中,再对64取模就能读取需访问的数据。这种方法又叫全关联映射很明显在查找时比较耗时,但是由于cache line只要有空闲就能填满因此空间利用率较高;
(2)将内存按照缓存大小进行分块,比如按照一级数据缓存大小32kb进行划分,4Gb的内存可以分为个块,然后每个块再按照cache line的大小64byte划分成个缓存行,与全关联不同,该方法中每个内存数据所在的数据块与缓存中的缓存行是一一对应的,不能像全关联一样,当有缓存行冲突的时候只要发现某个缓存行空闲就可以把内存中的数据加载过去,也就是说该方法即使有缓存行空闲着也不能用。与全关联一样每个缓存行需要额外的空间来记录内存地址所属的块号和缓存行的行号,该方法又叫直接映射;
(3)将缓存分成8份则每份4kb,将内存按照4kb进行分块,4Gb的内存可以分为个块,然后每个块再按照cache line的大小64byte划分成个缓存行,该方法与直接映射法很像只是没按照整个缓存大小进行划分,因此同样会出现缓存行冲突的问题,不同的是当发现第1份中的某个缓存行被占用时,会将数据加载到第2份中同一个缓存行行号中,如果第2份也被占用会依此类推到第3份中。这种方法又叫组关联映射法实际上是前两种方法的整合。该方法也就是目前cpu使用的最广泛的方法。
3、由组关联法可知,当第8份也被占用的时候就需要从1-8份里替换一个cache line了,替换的策略有随机替换法、先进先出替换法、最近最少被使用(简称LRU,通过记录每个cache line访问的时间将长时间没被访问的替换掉,考虑的是时间局部性)、最不经常使用(简称LFU,通过记录每个cache line被访问的次数,将次数最少的替换掉)。
来源:oschina
链接:https://my.oschina.net/u/1268334/blog/3066115