从程序员的视角看cache(一)


对cache的小结,参考:Computer System A programmer's Perspective 原版_第二版

x86查看cache信息:      sudo dmidecode -t cache 

locality:操作近期被其它指令所操作、或本身所操作的数或指令。

good temporal locality:被引用过的存储器在近期被多次引用

good spatial locality:被引用过的存储器的相邻存储器在近期被多次引用

数据的局部性适用于指令的局部性,但是我们很少修改指令,对于数据却是频繁的改动。

利用temporal locality,当发生第一次时出现miss,以后可以hit;利用spatial locality,当发生第一次时出现miss,将data所在的block中所有数据均放到cache中,再次取其临近数据时可以hit。

 

将K+1级的存储分成数个block,K级存储也划分成数个block,且K级block与K+1级block大小相同。K+1级与K级之间以block为数据量单位进行传输(相邻级的block大小相同,非相邻级其block不一定相同,低级的block较大,而高级的block较小)

 

cache的组成:

存取一个内存中的数据时需给出地址,假设为m位,那么:

  1. 将cache划分成2^s个sets;
  2. 每个set包含E个cache lines (block是传输的定长信息,cache line是block的容器,其中不仅存放block,而且还有其它存储单元,例如有效位——valid指示该line 中block是否有效,标签位——tag 来匹配标签,脏标志——dirty指示block数据是否发生变化……另外set是几个line的集合);
  3. 每个cache line中的block的数据偏移量2^b;
  4. t=m-(s+b)每个cache有t位tag  bits,来识别每个set中的cache line。

注意:1.m位地址被划分为 Tag:Set index:Block offset

        解释一下为什么会这样划分:

        如果Set Index在高位,那么大量的block极易映射到同一Set中,这样造成的conflict misses概率较大,cache性能会下降。Set Index更不可以放在低位,应为这样会造成缓存在block中的数据不能得到有效应用,例如:从0地址读一整型,假设block为16 bytes,那么4,8,12处数据将被同时放入cache,在读4处数据,此时由于Set Index为低位造成不从cache读取,而仍旧从内存读取,造成cache没有得到有效应用。所以Set Index在中间。

  2.实际上set所需的s位在硬件中并不存在。

cache组成可简单的以组元描述(S、E、B、m)

cache容量C=S*E*B

 

cache hit:若欲从K+1级模块中读d,则先在k的某块中寻找d,若找到,则cache hit

cache miss:与上相反。

           对于cache miss,会将K+1级中的d所在的块替换K级模块(已满),或者调入K级模块。若是替换,则将此过程称为replacing或者evicting,被替换的块称为victiming block。决定哪一块被替换,则称为replacement policy

 

cache misses 分类:

cold misses:不可避免。若K级cache空,则必发生cache miss,空的cache称为cold cache,这种cache misses称为compulsory misses或者cold misses。当cache已被warmed up则一般不会再发生cold misses。

conflict misses:多个K+1级的blocks被映射到K级中同一个block。这一点关系到对于程序员而言能否写出cache友好代码。

程序常会分阶段执行(例如循环:内层、外层),每个阶段会取cache blocks的固定几个块,

这几个块所构成的集合称为working set。

capacity misses:当working set超过cache大小时所发生的miss称为capacity misses。

 

发生cache misses时,K级Cache需实现placement policy来决定将K+1级的block放入K级的哪个block中。策略:

  1. 随机(代价太大,不能实现cache的用途);
  2. K+1级的某block只能被映射到K级的blocks中的某一个set中。

 

 

cache映射方式(E值):

E值较大的cache,其conflict miss概率小,但是代价太高(功耗、工艺、电路……),一般情况下K+1级的E值>K级E值。

  1.E=1:Direct_Mapped Caches (每个set中只有一个cache line)

  K+1级的block只能映射到K级相应set中唯一一个block。对于Directed_Mapped Caches发生misses时的替换策略非常简单:Set所在行必被替换。Directed_Mapped Caches中发生confilct misses的概率较大,cache性能没有得到充分利用,会造成thrashing:在一个cache中不停的loading或evicting。

  2.E=C/B,S=1:Fully Associative Caches

  对于这种cache,其电路结构复杂(因为tag需进行大量匹配测试,所以代价较高,因此一般只做比较小的cache,例如TLB)

  3.1<E<C/B,称为E-way set associative cache

  可以使得K+1级的cache映射到K级set中的某一个block。对于Set Associative Caches发生misses的line replacement该采取何种策略:若Set中有空或无效则直接替换;若Set中已满,则:随机(代价高)、LFU、LRU

 

上面讨论了读,以下总结针对写。

若要写的字w已在K级cache中,则称write hit,此时该采用何种策略更新K+1级中的w?

policy:

1.write-through:立即将w所在的K级cache block写回K+1级cache block 中,这回产生cache bus traffic,因此我们可以加入write buffer 来保存要更新的地址及其中的数据,write buffer由内存控制器自己控制,其是先进先出结构,以减少更新次数。据统计没10条指令将会执行一次写操作,因此在低速的CPU,这可以很好的工作,但是当CPU频率上升时,存储系统已不能以CPU的平均写速率来消化写操作。

2.write-back:尽量推迟将w所在的K级cache block写回K+1级cache block中,只有当K级block被evicted时才将更新的块写入K+1相应块中,这需要为每个block添加dirty bit位来指示block是否被更改。write-back策略可以显著的减少传输次数,由于越往下级的cache其传输时间更长,因此越往下级的cache更倾向于采用write-back而不是write-through(当然我们也可以采用write-through配以write-buffer来更加有效的使用cache)。

若要写的字w不在K级cache中,则称write misses,此时该采用何种策略更新K+1级中的w?

1.write_allocate:把K+1级相应先写入K级相应块,再更新已在K级的块。这样可以利用空间局部性。但是对于这种方式,每个misses都将数据由K+1先写入K级,要求cache性能高。

2.no_write_allocate:不经K级cache,而直接写入K+1级cache。

 

2012_11_18,读了老板的论文,记录下:

当处理器内核提出写操作的地址空间暂时没有被Cache缓冲时,即Cache发生写不命中时,其行为又可以分为写分配、读分配和读写分配三种策略。所谓读分配是指

数据仅在读操作不命中时由Cache进行行填充,从外存装载到Cache中;写分配是指数据仅在写不命中时由Cache从外存装载数据到Cache中;读写分配策略则不管时读缺失还是写缺失都将导致Cache的获取逻辑将数据从主存转载到Cache行中。

 

cache友好代码:

在一个对连续矢量操作的过程中,每隔K个元素进行一次操作称为stride-k pattern。当K变大时则空间布局性变差。从此处我们也可以看出数组元数在内存中的存放规则将影响我们的所采用算法的性能。

       若cache的block size为B bytes,一个stride-k(以words为单位)pattern导致平均错失率为min(1,(wordsize*k)/B)。

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM