函数调用带来的net err cache misss 会对 cpu 性能带来多大的影响

Cache line和Cache Block其实是一种东西,感谢下面那位答主的指正,是我当初被书里误导了QwQ&br&&br&———————分割线———————&br&这是之前我在一个问题评论时给别人讲的一段话,我再添加点东西:&br&&br&&br&&br&Cache主要是两部分——Tag和Data,Tag是存储一片连续数据的公共地址;Data就是存储这片连续地址的数据(存储器的空间局部性)而一个Tag和它对应的所有数据组成一行成为Cache line,Cache line中存储数据的叫Cache Data Block,然后如果一个Data可以存储进Cache的多个地方后被多个line寻址,则叫做Cache Set——Cache Set包括于若干个Cache line包括于若干个Data Block。&br&&br&&br&&br&至于你说的Index那是寻址问题:&br&&br&&br&我拿最简单的直接映射举例子:寻址时会调度四个部分——Tag、Index、Valid、Offset。&br&开始时使用Index寻址对应Cache line,这时候所有相同Index同时寻址到Cache line→之后调用Tag做比较得出Cache line需要的数据→这时候我们知道了我们真正想要取的Cache line然后进入→这时候寻址到了Block Offset→定位字节然后取出。&br&当然,Valid是用来标记line是否保存有效数据的,之前被访问过的存储器地址,它的Data才会存在对应的line里面,相应的有效位也会被置为1。多路组相连基本取字机制和直接映射差不多,但会有多选器选择和可能的流水化执行——Address Calculation→Disambiguation→Tag Access→Data Access→Result Drive,不过这个和题主问题没多少关系就不说了。&br&&br&基本结论:Index是一个索引,用来索引一个Cache Set中的多个Cache line中我们想要找的Cache line,而Cache line和Block的关系相当于若干个Block构成了一个Cache line
Cache line和Cache Block其实是一种东西,感谢下面那位答主的指正,是我当初被书里误导了QwQ ———————分割线——————— 这是之前我在一个问题评论时给别人讲的一段话,我再添加点东西: Cache主要是两部分——Tag和Data,Tag是存储一片连续数据的…
泻药,这是非常好的一个问题,同时也是比较前沿的。&br&&br&题目描述中的这个现象确实存在,已经有不少实测证明了,现在的L1 instruction miss率是比较差劲的,比较坏的会超过10%,instrcuction miss比较特殊,是乱序执行没办法掩盖的(乱序执行要调度不相干指令上来掩盖数据访问延迟,但是如果指令都取不上来也只能干瞪眼了。&br&&br&分条回答:&br&1. 是的,无可争议。&br&2. 假设一个完全不miss的L1 Intruction Cache,性能在有的benchmark上可以提高10%~50%&br&3. 不清楚,未见相关实测数据&br&4. 解决方案有两种,一种是编译优化时调整代码布局,这一个方向我没有跟进过不敢多说,另一个方向是由微结构负责从已经产生的miss中推断未来miss的位置,提前预取,我所知道的最好的研究结果是微结构研究重镇Umich和ARM联手发表在MICRO'13上的RDIP方案,实现了理想L1 Instruction Cache 70%左右的性能(注:在几个不太常见的benchmark上面得到的结果),代价是需要多开64KB的SRAM,这个额外存储空间比整个L1 Instruction Cache还大,所以没办法实用化,这个问题从研究走向工业实际估计还需要一段时间。
泻药,这是非常好的一个问题,同时也是比较前沿的。 题目描述中的这个现象确实存在,已经有不少实测证明了,现在的L1 instruction miss率是比较差劲的,比较坏的会超过10%,instrcuction miss比较特殊,是乱序执行没办法掩盖的(乱序执行要调度不相干指令上…
一句话解释,缓存太占地方,而且投入和回报不成正比,实在是不划算。&br&&br&一张图片就很明了了,三级缓存差不多占据了两个核心的面积,如果加上四级呢?五级呢?&br&要知道缓存容量都是急速递增的,你看图里看不到L1和L2 Cache吧, 如果有四级缓存,&br&单独的缓存面积就比整个现有的CPU大。&br&&img src=&/ae6cd097bba1ab409bd3a60_b.jpg& data-rawwidth=&2134& data-rawheight=&1060& class=&origin_image zh-lightbox-thumb& width=&2134& data-original=&/ae6cd097bba1ab409bd3a60_r.jpg&&&br&&br&那可能导致什么问题呢? &br&1. 同样的晶圆,原本能生产32块CPU,加上四级缓存可能10块都生产不了,价格暴涨没人买账。&br&2. 核心面积大,功耗大,发热量大,对散热设备要求高,与未来发展趋势对着干..&br&
而且核心面积大,良品率低,比较悲剧的是容易碎(我按碎过好多块)&br&3. 堆缓存这种方式,明显是土豪作风,要知道intel早期处理器连L3 Cache都没有。&br&
而且关键的问题是,你单纯的堆缓存,早晚有一天会混不下去,和之前攀主频的竞赛类似。&br&
还不如想办法去提升架构。&br&4. 对于整体性能的作用,L1 cache最大,L2次之, L3甚至不到L1 cache的十分之一。&br&
若是不计成本的话,加到L4还有情可原,加到L5其实已经和系统内存差不多了。
一句话解释,缓存太占地方,而且投入和回报不成正比,实在是不划算。 一张图片就很明了了,三级缓存差不多占据了两个核心的面积,如果加上四级呢?五级呢? 要知道缓存容量都是急速递增的,你看图里看不到L1和L2 Cache吧, 如果有四级缓存, 单独的缓存面积…
题主把CPU与Cache分的太开了, Cache已经是现代CPU的一部分. 从内存的角度来看, Cache发来的读数据请求就是CPU发来的读数据请求. 实际上CPU所有关于Load和Store的请求, 都会由MOB(Memory Order Buffer) - L1D - L2 - L3 -DRAM这条路径来完成, Cache只是这条路上的几个驿站而已.&br&&br&关于副标题内的疑问, 只用把问题逻辑顺一下就解决了。&br&&br&&b&既然CPU速率高, 内存速率慢,那么中间加一个Cache的目的就是为了让存储体系可以跟上CPU的速度.&/b&&br&普通的CPU+DRAM内存的结构, 如果没有设计Cache, 每一次CPU都要找内存要数据, 这个延迟估计在80个时钟周期左右. 这是因为CPU要从内部运算核心将请求发到CPU边缘的总线上, 从总线上电路板, 到达北桥, 再上电路板到达DRAM. DRAM搜索到数据后如此再送回去. CPU动作那么快, 等的黄花菜都凉了.&br&如果加了Cache会怎样? L1 Cache, 访问延迟在4个周期左右, L2 Cache, 延迟在15个周期左右, L3Cache, 延迟在50个周期左右. 但是由于添加了很多Cache, CPU访问内存的速度被降低了, 需要120个周期左右. 如果CPU需要的内容, 90%在L1 Cache里有, 6%在L2 Cache里有, 3%在L3 Cache里有, 1%要去找DRAM拿. 那么整个存储体系的等效延迟就是: 7.2个时钟周期.&br&这不是爽歪了么??&br&&br&&b&预先读取,为什么cpu不能做呢?&/b&&br&预取(prefetch)这件事cpu的确不做, 是Cache在做, 每一集的Cache都会有自己的prefetcher. 而实际上L1 Cache已经被融合进CPU内部里了, L1I和L1D和CPU流水线简直就是紧挨在一起, L2 Cache又紧挨着CPU的L1D. 所以L1I, L1D, L2它们做预取, 和CPU自己做是一回事! 而且CPU跑多快, 预取的速度就有多快!&br&上面凭什么说&CPU需要的内容, 90%在L1 Cache里有, 6%在L2 Cache里有&? 就是因为Cache中数据大多是复用的, 而且Cache基于历史数据还一直在预取! 而CPU和prefetcher像极了老总和小秘的关系, 比如:&br&&br&每次CPU缺什么数据, 找内存拿了一次, 但是这被prefetcher记住了, 等一有空prefetcher就把页(Page)中其他相关数据都从内存里拿出来. 这就等于把他七大姑八大姨街坊邻居全叫了出来, CPU提取数据一般是有范围的, 横竖不超过这么多, 所以CPU第二次再缺数据, 就不用去找内存了, 都在Cache里! 实际上Cache可以同时监控很多个页, 这个过程反复几次, CPU就基本不再找内存要东西了.
题主把CPU与Cache分的太开了, Cache已经是现代CPU的一部分. 从内存的角度来看, Cache发来的读数据请求就是CPU发来的读数据请求. 实际上CPU所有关于Load和Store的请求, 都会由MOB(Memory Order Buffer) - L1D - L2 - L3 -DRAM这条路径来完成, Cache只是这条…
已有帐号?
无法登录?
社交帐号登录
2940 人关注
131 条内容
2627 人关注
191 条内容
384 人关注
669 条内容
298 人关注
153 条内容   对于现代cpu而言,性能瓶颈则是对于内存的访问。cpu的速度往往都比主存的高至少两个数量级。因此cpu都引入了L1_cache与L2_cache,更加高端的cpu还加入了L3_cache.很显然,这个技术引起了下一个问题:
&&&&&&&& 如果一个cpu在执行的时候需要访问的内存都不在cache中,cpu必须要通过内存总线到主存中取,那么在数据返回到cpu这段时间内(这段时间大致为cpu执行成百上千条指令的时间,至少两个数据量级)干什么呢? 答案是cpu会继续执行其他的符合条件的指令。比如cpu有一个指令序列 指令1 &指令2 &指令3 &, 在指令1时需要访问主存,在数据返回前cpu会继续后续的和指令1在逻辑关系上没有依赖的&独立指令&,cpu一般是依赖指令间的内存引用关系来判断的指令间的&独立关系&,具体细节可参见各cpu的文档。这也是导致cpu乱序执行指令的根源之一。
&&&&&&&& 以上方案是cpu对于读取数据延迟所做的性能补救的办法。对于写数据则会显得更加复杂一点:
&&&&&&&& 当cpu执行存储指令时,它会首先试图将数据写到离cpu最近的L1_cache, 如果此时cpu出现L1未命中,则会访问下一级缓存。速度上L1_cache基本能和cpu持平,其他的均明显低于cpu,L2_cache的速度大约比cpu慢20-30倍,而且还存在L2_cache不命中的情况,又需要更多的周期去主存读取。其实在L1_cache未命中以后,cpu就会使用一个另外的缓冲区,叫做合并写存储缓冲区。这一技术称为合并写入技术。在请求L2_cache缓存行的所有权尚未完成时,cpu会把待写入的数据写入到合并写存储缓冲区,该缓冲区大小和一个cache line大小,一般都是64字节。这个缓冲区允许cpu在写入或者读取该缓冲区数据的同时继续执行其他指令,这就缓解了cpu写数据时cache miss时的性能影响。
当后续的写操作需要修改相同的缓存行时,这些缓冲区变得非常有趣。在将后续的写操作提交到L2缓存之前,可以进行缓冲区写合并。 这些64字节的缓冲区维护了一个64位的字段,每更新一个字节就会设置对应的位,来表示将缓冲区交换到外部缓存时哪些数据是有效的。当然,如果程序读取已被写入到该缓冲区的某些数据,那么在读取缓存数据之前会先去读取本缓冲区的。
经过上述步骤后,缓冲区的数据还是会在某个延时的时刻更新到外部的缓存(L2_cache).如果我们能在缓冲区传输到缓存之前将其尽可能填满,这样的效果就会提高各级传输总线的效率,以提高程序性能。
从下面这个具体的例子来看吧:
下面一段测试代码,从代码本身就能看出它的基本逻辑。
#include &unistd.h&
#include &stdio.h&
#include &sys/time.h&
#include &stdlib.h&
#include &limits.h&
static const int iterations = INT_MAX;
static const int items = 1&&24;
static int arrayA[1&&24];
static int arrayB[1&&24];
static int arrayC[1&&24];
static int arrayD[1&&24];
static int arrayE[1&&24];
static int arrayF[1&&24];
static int arrayG[1&&24];
static int arrayH[1&&24];
double run_one_case_for_8()
&&&&&&&& double start_
&&&&&&&& double end_
&&&&&&&& int i =
&&&&&&&& gettimeofday(&start, NULL);
&&&&&&&& while(--i != 0)
&&&&&&&& {
&&&&&&&&&&&&&&&&& int slot = i &
&&&&&&&&&&&&&&&&& int value =
&&&&&&&&&&&&&&&&& arrayA[slot] =
&&&&&&&&&&&&&&&&& arrayB[slot] =
&&&&&&&&&&&&&&&&& arrayC[slot] =
&&&&&&&&&&&&&&&&& arrayD[slot] =
&&&&&&&&&&&&&&&&& arrayE[slot] =
&&&&&&&&&&&&&&&&& arrayF[slot] =
&&&&&&&&&&&&&&&&& arrayG[slot] =
&&&&&&&&&&&&&&&&& arrayH[slot] =
&&&&&&&&&&&&&&&&&
&&&&&&&& }
&&&&&&&& gettimeofday(&end, NULL);
&&&&&&&& start_time = (double)start.tv_sec + (double)start.tv_usec/;
&&&&&&&& end_time = (double)end.tv_sec + (double)end.tv_usec/;
&&&&&&&& return end_time - start_
double run_two_case_for_4()
&&&&&&&& double start_
&&&&&&&& double end_
&&&&&&&& int i =
&&&&&&&& gettimeofday(&start, NULL);
&&&&&&&& while(--i != 0)
&&&&&&&& {
&&&&&&&&&&&&&&&&& int slot = i &
&&&&&&&&&&&&&&&&& int value =
&&&&&&&&&&&&&&&&& arrayA[slot] =
&&&&&&&&&&&&&&&&& arrayB[slot] =
&&&&&&&&&&&&&&&&& arrayC[slot] =
&&&&&&&&&&&&&&&&& arrayD[slot] =
&&&&&&&& }
&&&&&&&& i =
&&&&&&&& while(--i != 0)
&&&&&&&& {
&&&&&&&&&&&&&&&&& int slot = i &
&&&&&&&&&&&&&&&&& int value =
&&&&&&&&&&&&&&&&& arrayG[slot] =
&&&&&&&&&&&&&&&&& arrayE[slot] =
&&&&&&&&&&&&&&&&& arrayF[slot] =
&&&&&&&&&&&&&&&&& arrayH[slot] =
&&&&&&&& }
&&&&&&&& gettimeofday(&end, NULL);
&&&&&&&& start_time = (double)start.tv_sec + (double)start.tv_usec/;
&&&&&&&& end_time = (double)end.tv_sec + (double)end.tv_usec/;
&&&&&&&& return end_time - start_
int main()
&&&&&&&& mask = items -1;
&&&&&&&& printf("test begin----&\n");
&&&&&&&& for(i=0;i&3;i++)
&&&&&&&& {
&&&&&&&&&&&&&&&&& printf(" %d, run_one_case_for_8: %lf\n", i, run_one_case_for_8());
&&&&&&&&&&&&&&&&& printf(" %d, run_two_case_for_4: %lf\n", i, run_two_case_for_4());
&&&&&&&& }
&&&&&&&& printf("test end");
&&&&&&&& return 0;
相信很多人会认为run_two_case_for_4 的运行时间肯定要比run_one_case_for_8的长,因为至少前者多了一遍循环的i++操作。但是事实却不是这样:下面是运行的截图:
测试环境: fedora 20 64bits, 4G DDR3内存,CPU:Inter& Core& i7-3610QM cpu @2.30GHZ.
结果是令人吃惊的,他们的性能差距居然达到了1倍,太神奇了。
原理:上面提到的合并写存入缓冲区离cpu很近,容量为64字节,很小了,估计很贵。数量也是有限的,我这款cpu它的个数为4。个数时依赖cpu模型的,intel的cpu在同一时刻只能拿到4个。
因此,run_one_case_for_8函数中连续写入8个不同位置的内存,那么当4个数据写满了合并写缓冲时,cpu就要等待合并写缓冲区更新到L2cache中,因此cpu就被强制暂停了。然而在run_two_case_for_4函数中是每次写入4个不同位置的内存,可以很好的利用合并写缓冲区,因合并写缓冲区满到引起的cpu暂停的次数会大大减少,当然如果每次写入的内存位置数目小于4,也是一样的。虽然多了一次循环的i++操作(实际上你可能会问,i++也是会写入内存的啊,其实i这个变量保存在了寄存器上), 但是它们之间的性能差距依然非常大。
从上面的例子可以看出,这些cpu底层特性对程序员并不是透明的。程序的稍微改变会带来显著的性能提升。对于存储密集型的程序,更应当考虑到此到特性。
希望这篇文章能该大家带来一些帮助,也能可做性能优化的同事带来参考。
阅读(...) 评论()君,已阅读到文档的结尾了呢~~
分析影响cache命中率的因素因素分析法,因素分析
扫扫二维码,随身浏览文档
手机或平板扫扫即可继续访问
分析影响cache命中率的因素
举报该文档为侵权文档。
举报该文档含有违规或不良信息。
反馈该文档无法正常浏览。
举报该文档为重复文档。
推荐理由:
将文档分享至:
分享完整地址
文档地址:
粘贴到BBS或博客
flash地址:
支持嵌入FLASH地址的网站使用
html代码:
&embed src='/DocinViewer-4.swf' width='100%' height='600' type=application/x-shockwave-flash ALLOWFULLSCREEN='true' ALLOWSCRIPTACCESS='always'&&/embed&
450px*300px480px*400px650px*490px
支持嵌入HTML代码的网站使用
您的内容已经提交成功
您所提交的内容需要审核后才能发布,请您等待!
3秒自动关闭窗口cpu性能探究-Linux Cache机制 - 博客频道 - CSDN.NET
I know one thing, that I know nothing.
分类:操作系统

cpu性能探究-Linux Cache机制
在阅读文章前,您应该具备基本的存储器层次结构知识,至少要了解局部性原理。要详细了解cache基本原理,可以参考本书中存储器体系结构一章:
带着疑问来看文章,cache对于程序员是不可见的,它完全是由硬件控制的,为什么在linux内核中还有cache.h这个头文件,定义了一些关于cache的结构?
1. cache概述
cache,中译名高速缓冲存储器,其作用是为了更好的利用局部性原理,减少CPU访问主存的次数。简单地说,CPU正在访问的指令和数据,其可能会被以后多次访问到,或者是该指令和数据附近的内存区域,也可能会被多次访问。因此,第一次访问这一块区域时,将其复制到cache中,以后访问该区域的指令或者数据时,就不用再从主存中取出。
2. cache结构
假设内存容量为M,内存地址为m位:那么寻址范围为000…00~FFF…F(m位)
倘若把内存地址分为以下三个区间:
《深入理解计算机系统》p305
英文版 beta draft&
tag, set index, block offset三个区间有什么用呢?再来看看Cache的逻辑结构吧:
将此图与上图做对比,可以得出各参数如下:
现在来解释一下各个参数的意义:
一个cache被分为S个组,每个组有E个cacheline,而一个cacheline中,有B个存储单元,现代处理器中,这个存储单元一般是以字节(通常8个位)为单位的,也是最小的寻址单元。因此,在一个内存地址中,中间的s位决定了该单元被映射到哪一组,而最低的b位决定了该单元在cacheline中的偏移量。valid通常是一位,代表该cacheline是否是有效的(当该cacheline不存在内存映射时,当然是无效的)。tag就是内存地址的高t位,因为可能会有多个内存地址映射到同一个cacheline中,所以该位是用来校验该cacheline是否是CPU要访问的内存单元。
当tag和valid校验成功是,我们称为cache命中,这时只要将cache中的单元取出,放入CPU寄存器中即可。
当tag或valid校验失败的时候,就说明要访问的内存单元(也可能是连续的一些单元,如int占4个字节,double占8个字节)并不在cache中,这时就需要去内存中取了,这就是cache不命中的情况(cache miss)。当不命中的情况发生时,系统就会从内存中取得该单元,将其装入cache中,与此同时也放入CPU寄存器中,等待下一步处理。注意,以下这一点对理解linux cache机制非常重要:
当从内存中取单元到cache中时,会一次取一个cacheline大小的内存区域到cache中,然后存进相应的cacheline中。
例如:我们要取地址 (t, s, b) 内存单元,发生了cache miss,那么系统会取 (t, s, 00…000) 到 (t, s, FF…FFF)的内存单元,将其放入相应的cacheline中。
下面看看cache的映射机制:
当E=1时, 每组只有一个cacheline。那么相隔2^(s+b)个单元的2个内存单元,会被映射到同一个cacheline中。(好好想想为什么?)
当1&E&C/B时,每组有E个cacheline,不同的地址,只要中间s位相同,那么就会被映射到同一组中,同一组中被映射到哪个cacheline中是依赖于替换算法的。
当E=C/B,此时S=1,每个内存单元都能映射到任意的cacheline。带有这样cache的处理器几乎没有,因为这种映射机制需要昂贵复杂的硬件来支持。
不管哪种映射,只要发生了cache miss,那么必定会有一个cacheline大小的内存区域,被取到cache中相应的cacheline。
现代处理器,一般将cache分为2~3级,L1, L2, L3。L1一般为CPU专有,不在多个CPU中共享。L2 cache一般是多个CPU共享的,也可能装在主板上。L1 cache还可能分为instruction cache, data cache. 这样CPU能同时取指令和数据。
下面来看看现实中cache的参数,以Intel Pentium处理器为例:
L1 i-cache
L1 d-cache
3. cache miss的代价
cache可能被分为L1, L2, L3, 越往外,访问时间也就越长,但同时也就越便宜。
L1 cache命中时,访问时间为1~2个CPU周期。
L1 cache不命中,L2 cache命中,访问时间为5~10个CPU周期
当要去内存中取单元时,访问时间可能就到25~100个CPU周期了。
所以,我们总是希望cache的命中率尽可能的高。
4. False Sharing(伪共享)问题
到现在为止,我们似乎还没有提到cache如何和内存保持一致的问题。
其实在cacheline中,还有其他的标志位,其中一个用于标记cacheline是否被写过。我们称为modified位。当modified=1时,表明cacheline被CPU写过。这说明,该cacheline中的内容可能已经被CPU修改过了,这样就与内存中相应的那些存储单元不一致了。因此,如果cacheline被写过,那么我们就应该将该cacheline中的内容写回到内存中,以保持数据的一致性。
现在问题来了,我们什么时候写回到内存中呢?当然不会是每当modified位被置1就写,这样会极大降低cache的性能,因为每次都要进行内存读写操作。事实上,大多数系统都会在这样的情况下将cacheline中的内容写回到内存:
当该cacheline被置换出去时,且modified位为1。
现在大多数系统正从单处理器环境慢慢过渡到多处理器环境。一个机器中集成2个,4个,甚至是16个CPU。那么新的问题来了。
以Intel处理器为典型代表,L1级cache是CPU专有的。
先看以下例子:
系统是双核的,即为有2个CPU,CPU(例如Intel Pentium处理器)L1 cache是专有的,对于其他CPU不可见,每个cacheline有8个储存单元。
我们的程序中,有一个 char arr[8] 的数组,这个数组当然会被映射到CPU L1 cache中相同的cacheline,因为映射机制是硬件实现的,相同的内存都会被映射到同一个cacheline。
<span style="color:#个线程分别对这个数组进行写操作。当0号线程和1号线程分别运行于0号CPU和1号CPU时,假设运行序列如下:
开始CPU 0对arr[0]写;
随后CPU 1对arr[1]写;
随后CPU 0对arr[2]写;
CPU 1对arr[7]写;
根据多处理器中cache一致性的协议:
当CPU 0对arr[0]写时,8个char单元的数组被加载到CPU 0的某一个cacheline中,该cacheline的modified位已经被置1了;
当CPU 1对arr[1]写时,该数组应该也被加载到CPU 1的某个cacheline中,但是该数组在cpu0的cache中已经被改变,所以cpu0首先将cacheline中的内容写回到内存,然后再从内存中加载该数组到CPU 1中的cacheline中。CPU 1的写操作会让CPU 0对应的cacheline变为invalid状态注意,由于相同的映射机制,cpu1 中的 cacheline
和cpu0 中的cacheline在逻辑上是同一行(直接映射机制中是同一行,组相联映射中则是同一组)
当CPU 0对arr[2]写时,该cacheline是invalid状态,故CPU 1需要将cacheline中的数组数据传送给CPU 0,CPU 0在对其cacheline写时,又会将CPU 1中相应的cacheline置为invalid状态
如此往复,cache的性能遭到了极大的损伤!此程序在多核处理器下的性能还不如在单核处理器下的性能高。
对于伪共享问题,有2种比较好的方法:
1. 增大数组元素的间隔使得由不同线程存取的元素位于不同的cache line上。典型的空间换时间
2. 在每个线程中创建全局数组各个元素的本地拷贝,然后结束后再写回全局数组
而我们要说的linux cache机制,就与第1种方法有关。
5. Cache友好的代码
Cache友好的代码,简单地说,
减小cache miss率 在多核环境下,减小乃至消除“伪共享”问题发生的概率。
在单核环境下,有一个典型的例子:
Cache友好的代码:
int sumarrayrows(char a[M][N])
int i, j, sum = 0;
for (i = 0; i & M; i&#43;&#43;)
for (j = 0; j & N; j&#43;&#43;)
sum &#43;= a[i][j];
由于一般的机器中,C语言数组都是按行优先存储的。假设Cacheline的大小为B个字节,Cache总容量为C字节,直接映射存储方式,那么一共有C/B行Cacheline。对于a[M][N]这个M*N个字节。每每读到第 n*B 个数组元素时( 0&n&M*N/B ),才会发生cache miss,因此至多发生 M*N/B 次cache miss,不命中率至多为 (M*N/B)/(M*N) = 1/B。
Cache不友好的代码:
int sumarraycols(char a[M][N])
int i, j, sum = 0;
for (j = 0; j & N; j&#43;&#43;)
for (i = 0; i & M; i&#43;&#43;)
sum &#43;= a[i][j];
这个代码是按列优先访问的,情况要复杂一些。我们只看一种比较简单的情况:
当 N=B , M*N & C, E是cacheline的行数,即为C/B。 看看会发生什么:在访问a[0][0]~a[E-1][0]时,每次都会造成Cache miss,然后访问a[E][0]~a[M-1][0]时,又会把第0~M-E-1行cacheline给覆盖掉,因此当访问a[0][0]~a[M-1][0]时总是会造成Cache miss。在访问a[0][1]~a[M-1][1]时,分为2个过程,前0~M-E-1行由于被覆盖了,故而Cache又会不命中,而在第M-E~E-1行中, 也就是访问a[M-E][1]~a[E-1][1]时,由于没有被覆盖,这些行将会命中。因此总共有
M&#43;2*(M-E)*(N-1)次cache miss。不命中率可算得:2-2E/M-1/N&#43;2E/(M*N)。可见,当M&=2E时,不命中率&=1。
当赋予M,N较大&#20540;时,测试结果将会是列优先访问程序的运行时间远远大于行优先访问程序运行时间。
多核环境下,只要不同的线程或者进程访问同一cacheline的不同内容,就会发生“伪共享问题”。这样的问题较为隐蔽,难以发现。
6. GCC Attribute
7. 头文件&linux/cache.h&解读
代码就不贴了
a. L1_CACHE_ALIGN(x)这个宏
#define L1_CACHE_ALIGN(x) ALIGN(x, L1_CACHE_BYTES)
// linux/kernel.h
#define __ALIGN_KERNEL(x, a) __ALIGN_KERNEL_MASK(x, (typeof(x))(a) - 1)
#define __ALIGN_KERNEL_MASK(x, mask) (((x) &#43; (mask)) & ~(mask))
该宏返回x所指向的内存区域的起始的cacheline的边界地址。
b. ____cacheline_aligned宏
#define SMP_CACHE_BYTES L1_CACHE_BYTES
#define ____cacheline_aligned __attribute__((__aligned__(SMP_CACHE_BYTES)))
该宏是gcc属性,对定义的数据结构做空间对齐,使之起始位置对齐cacheline
c. __cacheline_aligned宏
#define __cacheline_aligned \
__attribute__((__aligned__(SMP_CACHE_BYTES), \
__section__(&.data..cacheline_aligned&)))
把数据分配到data段的cacheline_aligned子段里面,并且数据的起始位置对齐cacheline。.data..cacheline_aligned的定义在arch/XXX/kernel/vmlinuz.lds.S下面,有兴趣的读者可以自行查阅代码
b和c宏看起来很类&#20284;,只差了2个下划线而已,区别在于前者用于局部数据的声明,后者声明于全局数据,可以放在.data段
有一些在多处理器体系结构下的关键数据结构,就是用cacheline_aligned来声明的,譬如:
// linux&#43;v3.1.1/arch/ia64/kernel/numa.c#L27
u16 cpu_to_node_map[NR_CPUS] __cacheline_
这样能够避免每个CPU在对属于自己的那个map读写时造成false sharing问题
排名:第568名
(114)(2)(1)(1)(91)(213)(7)(41)(13)(9)(37)(5)(1)

我要回帖

更多关于 cache miss 的文章

 

随机推荐