|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
c语言的编译器,几乎是所有新平台都有的。因此从这点上看,c语言的程序,比其他任何语言更加容易跨平台。比来在用vtune剖析程序功能瓶颈时,发明一些内存会见的中央居然成了cpu热门。经由细心剖析,发明这些热门次要是对年夜数组非一连地位的会见的引发的。对照损耗cpu的缘故原由应当是cache不射中。由于像如许部分性很差的内存会见逻辑,对cache是很不友爱的。因而想到了prefetch……
x86(和其他良多系统布局)的CPU供应了prefetch系列指令,用于将指定地点的内存预取到cache。如”prefetcht0(%rax)”将以$rax所保留的值为地点的内存地点的cacheline(巨细通常为64byte)载进每级cache。
在得当地位加了prefetch以后,程序里响应的cpu热门公然得以打消,程序功能失掉提拔。
征象
在此也写一段测试程序,体验一下prefetch的功能,并做一些复杂的剖析。(注,剖析硬件的举动其实是一件疾苦的事变。关于软件来讲,源代码摆在那边,一是1、二是二,良多成绩都是断定的。而硬件不但看不到它的详细完成,也鲜有文档。而且比拟操纵体系为软件供应的假造而纯真的运转情况,硬件情况庞大多变,偶然候其实让人难以揣摩。以是以下剖析其实不免存在错误。不当的地方还请包容。)- #include<xmmintrin.h>#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<sys/mman.h>#include<sys/time.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<math.h>voidusage(){printf("usage:BINfilestepprefetchn");exit(1);}inlineintcalcu(intinput){#ifdefEMPTYCALCreturninput;#endifintval=(input%99)*(input/98);val=val?val:1;#ifdefHEAVYCALCdoubled=(double)input/(double)val;return(int)pow(d,1999.9);#endifdoublen=sqrt(sqrt((double)(unsigned)input*1.3));doublem=sqrt(sqrt((double)(unsigned)val*0.9));return(int)((double)input*(double)val*m/(n?n:1.1));}intrun_withprefetch(constint*array,intsize,intstep,intprefetch){intresult=0;printf("runwithprefetch(%d)...n",prefetch);for(inti=0;i<step;i++){for(intj=i;j<size;j+=step){intk=j+step*prefetch;if(k<size){_mm_prefetch(&array[k],_MM_HINT_T0);//constint*addr=&array[k];//__asm____volatile__("mov(%0),%%eax"::"r"(addr):"eax");//__asm____volatile__("mov%0,%%eax"::"r"(k):"eax");}result+=calcu(array[j]);}}returnresult;}intrun(constint*array,intsize,intstep){intresult=0;printf("run...n");for(inti=0;i<step;i++){for(intj=i;j<size;j+=step){//asmvolatile("lfence");result+=calcu(array[j]);}}returnresult;}intmain(intargc,constchar*argv[]){if(argc!=4){usage();}intstep=atoi(argv[2]);intprefetch=atoi(argv[3]);intfd=open(argv[1],O_RDONLY);if(fd==-1){usage();}structstatst;intret=fstat(fd,&st);if(ret!=0){usage();}intarray_size=st.st_size/sizeof(int);printf("arraysize:%d,step:%d.",array_size,step);constint*array=(constint*)mmap(NULL,st.st_size,PROT_READ,MAP_POPULATE|MAP_SHARED,fd,0);if(array==MAP_FAILED){usage();}structtimevaltv1,tv2;gettimeofday(&tv1,NULL);intresult=0;if(prefetch==0){result=run(array,array_size,step);}elseif(prefetch>0){result=run_withprefetch(array,array_size,step,prefetch);}gettimeofday(&tv2,NULL);tv2.tv_sec-=tv1.tv_sec;tv2.tv_usec-=tv1.tv_usec;if(tv2.tv_usec<0){tv2.tv_usec+=1000000;tv2.tv_sec--;}printf("timecost:%d.%06d,",tv2.tv_sec,tv2.tv_usec);printf("result:%dn",result);returnresult;}
复制代码 程序mmap一个年夜文件(由file参数指定)作为年夜数组,对数组中的每个元素举行必定的变更逻辑(calc函数,经由过程宏界说选择三种分歧庞大度的逻辑)、然后加和。
关于数组元素的会见撑持按次会见和腾跃会见(step参数,腾跃间隙)、撑持预取(prefetch参数,预取提早量)。
(注重,程序终极发生的result的值只跟选择的盘算逻辑和输出文件的内容相干,跟读内存的按次有关。以是,后续不论给程序加了甚么八怪七喇的读内存逻辑,终极的result都是分歧的。这就确保了我们所加的各类读内存逻辑没有引进BUG。)
一些测试了局:- $ll-htest.tar.gz-rw-rw-r--1xiangyxiangy1.8GJun2709:37test.tar.gz$g++-O2prefetch.cpp-DHEAVYCALC-oprefetch.heavy$g++-O2prefetch.cpp-DEMPTYCALC-oprefetch.empty$g++-O2prefetch.cpp-oprefetch.normal$./prefetch.normalusage:BINfilestepprefetch
复制代码 (选择分歧庞大度的盘算逻辑,编译成以分歧后缀定名的可实行文件。)- [case-1]$./prefetch.emptytest.tar.gz10240arraysize:468787200,step:1024.run...timecost:23.980005,result:692002678
复制代码 (空盘算+跳读内存。预期内存会见基础上都是cachemiss,而盘算逻辑基础上又不花工夫,以是终极消费的工夫次要就是读内存工夫。记着这个值,上面的case常常必要参考它。)- [case-2]$./prefetch.normaltest.tar.gz10arraysize:468787200,step:1.run...timecost:22.846302,result:1309150882[case-3]$./prefetch.normaltest.tar.gz10240arraysize:468787200,step:1024.run...timecost:66.041256,result:1309150882[case-4]$./prefetch.normaltest.tar.gz10244arraysize:468787200,step:1024.runwithprefetch(4)...timecost:28.247350,result:1309150882
复制代码 (以上是一般盘算的运转情形。case-2按次读内存预期基础上都是cachehit的,终极消费的工夫次要是实行盘算逻辑的工夫;case-3跳读内存工夫消费大批增添;case-4加了预取以后,工夫消费基础上恢复到case-2的程度。)- [case-5]$./prefetch.heavytest.tar.gz10arraysize:468787200,step:1.run...timecost:47.386533,result:1625037789[case-6]$./prefetch.heavytest.tar.gz10240arraysize:468787200,step:1024.run...timecost:107.783801,result:1625037789[case-7]$./prefetch.heavytest.tar.gz10244arraysize:468787200,step:1024.runwithprefetch(4)...timecost:51.492479,result:1625037789
复制代码 (以上是庞大盘算的运转情形。跟后面的体现基础分歧,跳读带来了大批的工夫增加,而预取又基础恢复到按次读时的程度。)
假如读内存开支很小、大概盘算开支很小,prefetch也有效么?- [case-8]$./prefetch.emptytest.tar.gz10244arraysize:468787200,step:1024.runwithprefetch(4)...timecost:24.253892,result:692002678
复制代码 (空盘算+跳读内存,预取效果跟不加预取时差未几。)- [case-9]$./prefetch.normaltest.tar.gz14arraysize:468787200,step:1.runwithprefetch(4)...timecost:22.896790,result:1309150882
复制代码 (一般盘算+按次读内存+预取,效果跟不加预取时也差未几。)
可见当读内存存在必定开支、且开支小于或相称于盘算开支时,经由过程得当的prefetch可以将跳读内存开支埋没失落,基础上到达按次读内存的效果。
反过去假如盘算开支不年夜、大概读内存自己没甚么开支,纯真想经由过程prefetch来提拔读内存的速率,效果其实不分明。
prefetch道理
为何prefetch能到达如许的效果呢?复杂来讲,prefetch将底本串行事情的盘算历程和读内存历程并行化了。如:- load-1load-1calc-1=>calc-1load-2load-2calc-2load-3calc-2calc-3
复制代码 可是实践上却又并不是云云复杂。
在一个程序中,构成程序自己的指令固然是有按次的,可是CPU在实行指令的时分其实不必定墨守成规一条一条的往实行,指令之间有良多并行性能够往发掘。(这就是所谓的ILP,InstructionLevelParallelism,指令级并行。)
两条指令想要并行实行必要满意三个前提:
1、指令之间没无数据依附
如:a=b*3;c=d+5;,就没有依附;
如:a=b*3;c=a+5;,“写后读”依附。第二条指令必要a的值作为输出,而a的值依附于第一条指令的盘算了局;
如:a=b*3;a=d+5;,“写后写”依附。不外固然第二条指令必定要在第一条指令修正a的值以后才干修正a的值(确保终极a的值是d+5的了局),可是实在两条指令是能够并行实行的,最初将了局commit到a的时分再串行就OK了;
如:a=b*3;b=d+5;,“读后写”依附。一样,固然第二条指令必定不克不及在第一条指令读取b的值之前就将b的值修正(确保第一条指令读到的是旧值),可是只需确保第一条指令先拿到b的旧值、大概间接跟天生b的旧值的那条指令联系关系上,以后两条指令仍是能够并行实行的;
2、CPU功效部件充分
CPU顶用来实行详细操纵的功效部件是无限的,假定CPU只要一个乘法器。
如:a=b*3;c=d+5;,一个利用乘法器、另外一个利用加法器,互不影响就能够并行;
如:a=b*3;c=d*5;,两条指令都必要利用这个唯一的乘法器,就只能串行了(固然也一定是第一条指令先占用乘法器,由于大概它所依附的b的值还没有ready、而第二条指令所必要的d已OK);
3、CPU已看到这两条指令
程序实行的指令序列大概无量无尽(思索到有轮回),CPU为了发掘并行性不成能一会儿剖析一切指令,必定会有个限制。在典范的流水线算法--tomasulo算法--中,这个限制就是RS(reservationstation,保存站)的数量。CPU取指部件按指令按次将指令放进RS,并设置它们的依附干系(RS供应了如许的撑持)。存在于RS中的指令当输出已ready、且必要的功效部件有空余时,便会入手下手实行。
碰到分支指令时指令序列怎样断定呢?分支指令在实行完成之前基本就不晓得程序要往那里走。办理举措就是分支展望,依据一些统计信息推测程序的走向。然后忽视这些分支,就跟没有分支时一样往实行。假如分支展望错了,再回滚返来,按准确的序列从头实行。
回到我们的例子,每一个loop有一个load历程+一个calc历程,calc历程依附于load历程。可是下一次loop的load其实不依附于上一loop的calc历程,而且load和calc利用分歧的CPU功效部件,以是这两个历程是能够并行的。(for轮回在履历良多次loop以后才会加入,每次分支的走向都是一样的,能够近似以为分支展望必定乐成。)
prefetch加与不加,前两个并行前提都是不会变的:prefetch既不会改动指令之间的依附干系、也不会多占用或少占用CPU功效部件。可是为何加上prefetch会无效果呢?
区分只在于第三个并行前提。试想当程序实行到第N次loop时(loop-N),因为calc历程庞大,指令良多,RS一向被占满。直到盘算进进序幕,loop-(N+1)的指令才进进RS,这时候CPU才晓得要往loadloop-(N+1)的input。而这些input在cache中不克不及射中,必要履历冗长的读内存历程,招致loop-(N+1)的calc指令卡在RS中得不到实行。相称于load和calc历程被串行化了。
加了prefetch以后呢?loop-(N+X)的prefetch指令排在loop-N的盘算指令之前,早早的就可以进进RS。这些load指令是盘算的泉源,其自己并没有依附其余数据,以是一旦内存通道余暇上去prefetch就能够入手下手事情了。因而load与calc才正真并行起来。
以下面表示:
case-2,按次读内存,cache全射中:- calc-11calc-12calc-13calc-21calc-22calc-23calc-31calc-32calc-33calc-41calc-42calc-43calc-51calc-52calc-53
复制代码 (每一个loop约消费2个单元工夫。)
case-3,跳读内存,cache全不射中:- load-11load-12calc-11calc-12calc-13load-21load-22calc-21calc-22calc-23load-31load-32calc-31calc-32calc-33
复制代码 (每一个loop约消费4个单元工夫。注,假定CPU实行到calc-N3的时分才看到有load-(N+1)1。)
case-4,跳读内存,加预取:- $ll-htest.tar.gz-rw-rw-r--1xiangyxiangy1.8GJun2709:37test.tar.gz$g++-O2prefetch.cpp-DHEAVYCALC-oprefetch.heavy$g++-O2prefetch.cpp-DEMPTYCALC-oprefetch.empty$g++-O2prefetch.cpp-oprefetch.normal$./prefetch.normalusage:BINfilestepprefetch0
复制代码 (每一个loop约消费2个单元工夫。注,在calc-N1之前prefetch-(N+X)1就已倡议了。)
经由过程prefetch,使这些既耗时又被后续指令依附的load指令提早进进CPU的视野,让CPU能够使用大概余暇的内存带宽,提早完成读操纵。另外一方面,利用prefetch预取内存以后,跟依附于它的那些盘算指令拉开了间隔,使得盘算指令不用比及即刻就得利用load的输出时才一时抱佛脚。拉开相干依附指令的间隔恰是编译器优化代码的一种经常使用手腕,一般经由过程指令重排调剂有关指令的按次来完成。
到这里prefetch的大抵逻辑已理分明了,可是细心想一下实在成绩还良多……
例子引出的成绩
第一个成绩,为何case-3(读内存+一般盘算)的实行工夫(66.041256)要比case-1(纯真的读内存)工夫(23.980005)+case-2(纯真的盘算)工夫(22.846302)更长呢?按理说,就算load指令和calc指令完整串行,case-3的实行工夫最多也就即是1、2之合吧。
这个成绩应当能够用内存多通道来注释。如今CPU会见内存的通道一样平常会有两个或以上。在case-1中,纯真的读内存实在其实不代表串行的读内存。多个内存通道是可让多个load指令并行事情的,以充实使用内存带宽。而在case-3中,因为引进了一堆盘算指令,招致RS被装满,CPU没法同时看到以后loop和下一个loop的load哀求,也就没法将两次load并行化。以是,更正确来讲,case-3耗时这么长的缘故原由并非load与calc没法并行,而是load与load没法并行。loop-N的calc历程跟loop-(N+1)的load历程是并行的,可是在loop-(N+1)load完成并举行calc之前,loop-(N+2)的load指令还未进进CPU的视野,以是没法与loop-(N+1)的load并行。
怎样证明这一点呢?
我们在case-1中加一个lfence指令碰运气。lfence是x86供应的内存屏蔽指令,感化是确保load操纵的按次:lfence之前的load操纵必需先于lfence以后的load操纵。云云便冲破了load的并行性(假如真如方才所说,并行性存在的话)。
修正run函数以下:- $ll-htest.tar.gz-rw-rw-r--1xiangyxiangy1.8GJun2709:37test.tar.gz$g++-O2prefetch.cpp-DHEAVYCALC-oprefetch.heavy$g++-O2prefetch.cpp-DEMPTYCALC-oprefetch.empty$g++-O2prefetch.cpp-oprefetch.normal$./prefetch.normalusage:BINfilestepprefetch1
复制代码 再次运转case-1:- $ll-htest.tar.gz-rw-rw-r--1xiangyxiangy1.8GJun2709:37test.tar.gz$g++-O2prefetch.cpp-DHEAVYCALC-oprefetch.heavy$g++-O2prefetch.cpp-DEMPTYCALC-oprefetch.empty$g++-O2prefetch.cpp-oprefetch.normal$./prefetch.normalusage:BINfilestepprefetch2
复制代码 (强迫让load不克不及并行以后,case-1.1的耗时间接酿成了case-3的程度。申明在底本的case-1中load是存在很年夜的并行度的。)
再以加lfence的代码运转一下case-6(未加prefetch、庞大盘算)看看,假如在庞大盘算+跳读内存的情形下,读内存的并行性已很少的话,加了lfence以后的耗时应当跟加上前差未几:- $ll-htest.tar.gz-rw-rw-r--1xiangyxiangy1.8GJun2709:37test.tar.gz$g++-O2prefetch.cpp-DHEAVYCALC-oprefetch.heavy$g++-O2prefetch.cpp-DEMPTYCALC-oprefetch.empty$g++-O2prefetch.cpp-oprefetch.normal$./prefetch.normalusage:BINfilestepprefetch3
复制代码 (公然云云。)
还能够同时运转两个程序来看看是甚么情形。两个程序同时运转时,因为kernelloadbalance的感化,它们会只管运转在分歧的CPU上、且只管不共享cache。那末,假如两个历程都老是能cachehit,则运转工夫应当跟单个历程运转时差未几;反之假如老是cachemiss,则两个历程会争抢内存带宽,运转工夫会有所增添。- $ll-htest.tar.gz-rw-rw-r--1xiangyxiangy1.8GJun2709:37test.tar.gz$g++-O2prefetch.cpp-DHEAVYCALC-oprefetch.heavy$g++-O2prefetch.cpp-DEMPTYCALC-oprefetch.empty$g++-O2prefetch.cpp-oprefetch.normal$./prefetch.normalusage:BINfilestepprefetch4
复制代码 (两个按次读内存的一般盘算一同运转,由于老是cachehit,以是跟单个运转的工夫差未几。)- $ll-htest.tar.gz-rw-rw-r--1xiangyxiangy1.8GJun2709:37test.tar.gz$g++-O2prefetch.cpp-DHEAVYCALC-oprefetch.heavy$g++-O2prefetch.cpp-DEMPTYCALC-oprefetch.empty$g++-O2prefetch.cpp-oprefetch.normal$./prefetch.normalusage:BINfilestepprefetch5
复制代码 (两个加了lfence的历程一同运转,因为历程内的内存会见已串行化了,两个历程能够各自利用一个内存通道,以是运转工夫跟单个历程运转时差未几。)- $ll-htest.tar.gz-rw-rw-r--1xiangyxiangy1.8GJun2709:37test.tar.gz$g++-O2prefetch.cpp-DHEAVYCALC-oprefetch.heavy$g++-O2prefetch.cpp-DEMPTYCALC-oprefetch.empty$g++-O2prefetch.cpp-oprefetch.normal$./prefetch.normalusage:BINfilestepprefetch6
复制代码 (而用之前没加过lfence的程序再试一下,两个历程同时运转时,因为争抢内存带宽,运转工夫就会受影响。)
prefetch提早量
另有一个成绩也能够用内存多通道来注释,即prefetch提早量成绩。先就之前的程序持续看几个case:- $ll-htest.tar.gz-rw-rw-r--1xiangyxiangy1.8GJun2709:37test.tar.gz$g++-O2prefetch.cpp-DHEAVYCALC-oprefetch.heavy$g++-O2prefetch.cpp-DEMPTYCALC-oprefetch.empty$g++-O2prefetch.cpp-oprefetch.normal$./prefetch.normalusage:BINfilestepprefetch7
复制代码 prefetch提早量从1增年夜到32。从了局看,当提早量小的时分,prefetch效果不分明。为何呢?
假定提早量为1,那末loop-N会为loop-(N+1)举行预取。可是夙昔面一般盘算的数据能够看出,就一个loop而言,load的工夫是多于calc工夫的(从总量上说,load并行以后才与calc工夫相称,那末独自的load就应当比calc耗时长)。以是当实行到loop-(N+1)的时分,prefetch应当还没有完成。
再假定提早量为4,loop-N会为loop-(N+4)做预取,loop-(N+1)为loop-(N+5)预取。而在进进loop-(N+1)时,loop-(N+4)的预取还没有完成,而此时倡议的loop-(N+5)的预取就可以与之并行。可见增年夜提早量能更好的使用内存带宽。(固然说以N为提早量就能够充实使用N个内存通道,可是呆板上另有kernel和其他历程也在利用内存,一定就可以让你独有内存带宽。以是利用年夜于N的提早量更能充实使用空余的内存带宽。)
固然提早量一定不克不及太年夜,不然等真正用到数据的时分,预取好的数据大概已被从cache中挤进来了。
用mov取代prefetch?
prefetch指令能够用来预取,岂非不必prefetch就不可了么?
我们将之前的run_withprefetch函数修正一下,把prefetch交换成复杂的load操纵:- $ll-htest.tar.gz-rw-rw-r--1xiangyxiangy1.8GJun2709:37test.tar.gz$g++-O2prefetch.cpp-DHEAVYCALC-oprefetch.heavy$g++-O2prefetch.cpp-DEMPTYCALC-oprefetch.empty$g++-O2prefetch.cpp-oprefetch.normal$./prefetch.normalusage:BINfilestepprefetch8
复制代码 重跑case-4:- $ll-htest.tar.gz-rw-rw-r--1xiangyxiangy1.8GJun2709:37test.tar.gz$g++-O2prefetch.cpp-DHEAVYCALC-oprefetch.heavy$g++-O2prefetch.cpp-DEMPTYCALC-oprefetch.empty$g++-O2prefetch.cpp-oprefetch.normal$./prefetch.normalusage:BINfilestepprefetch9
复制代码 的确比不加prefetch的情形case-3(66.041256)要好良多,但仍是比不上本来的case-4(28.247350)。
那末prefetch比间接movload幸亏那里呢?
1、利用mov也一样能到达让load操纵提早进进CPU视野的目标
2、利用mov会见过的内存一样会被cache住
3、仅仅是由于mov操纵多占用了一个存放器么?把代码改成如许看看(利用本来的prefetch可是多占用一个存放器):- [case-1]$./prefetch.emptytest.tar.gz10240arraysize:468787200,step:1024.run...timecost:23.980005,result:6920026780
复制代码- [case-1]$./prefetch.emptytest.tar.gz10240arraysize:468787200,step:1024.run...timecost:23.980005,result:6920026781
复制代码 可见仅仅多占用一个存放器,貌似并没有甚么影响。(在tomasulo算法中,这里实践上并没有多占用存放器,而是多占用了RS。)
后面提到了tomasulo算法,提到了RS,这内里另有个工具叫ROB(reorderbuffer,指令重排缓存)。后面还提到过关于“写后写”依附指令,在实行过程当中是能够并行的,只需包管最初写回的按次稳定就好了。ROB就可以完成这个功效。
CPU取指令以后除将其放进RS(让其能够乱序实行),还要按按次将其放进ROB。实行完成后的指令终极在ROB中列队,然后按按次提交(将了局写回存放器或内存)。ROB另有另外一个很主要的感化,就是分支展望失利时的回滚。分支指令也跟其他指令一样要在ROB中列队。假如分支指令实行完今后发明分支展望错了,则将ROB里排在这条分支以后的指令及其了局都清算失落就好了。由于ROB是按指令按次列队的,因为分支展望堕落而被毛病实行的那些指令必定都排在分支指令以后。
回到我们的例子,”mov(addr),%eax”这条指令会一向占着ROB,直到load完成。这将招致后续的指令了局一向得不到提交,还没有被CPU取走的指令又会由于没法取得ROB而不克不及被取走。这又回到了相似于case-3(未加prefetch)的情况,指令没法大批进进CPU的视野,乃至于内存会见没法占满内存带宽。只不外由于ROB的资本没有RS那末严重,以是堵塞的情形没有case-3那末严峻。
基于以上申明,我们改革case-6(未加prefetch、庞大盘算)看看。关于庞大盘算,calc历程的指令更多,按理说,堵塞的情形会更严峻,间接load应当效果更差。- [case-1]$./prefetch.emptytest.tar.gz10240arraysize:468787200,step:1024.run...timecost:23.980005,result:6920026782
复制代码 公然,这个了局比起底本的case-6(107.783801)已没有上风了。
那末为何prefetch不会受ROB的巨细限定呢?由于prefetch是一个特别指令,没有输入,对程序高低文也没有影响,乃至于分支展望失利时也不必要回滚。那末CPU完整没需要让prefetch指令进ROB(固然RS仍是要进的,由于prefetch大概依附于后面指令的了局)。
其他成绩
关于硬件prefetch
固然CPU供应了显式prefetch指令,实在它本人黑暗也会举行一些prefetch,能够称之为硬件prefetch。
硬件prefetch有这么几个要点:
1、CPU在履历一连必定次数的cachemiss后触发。偶然产生的一次cachemiss是很一般的,好比会见一个不常利用的全局变量;
2、CPU有必定的形式婚配战略,可以辨认按次会见和一些流动step的腾跃会见;
3、最主要的一点,硬件prefetch不会跨page举行。由于内存是按page办理的,跨page意味着大概触发pagefault,这是CPU本人所没法handle的,得由kernel来办理。CPU黑暗的prefetch举措对软件来讲原本是通明的,不克不及让kernel往handle大概本不该产生的pagefault,乃至于如许的pagefault大概招致segmentfault(相反,软件prefetch是软件本人倡议的,有甚么成果本人承当);
基于第3点,CPU一样平常不会试图往辨认步长高于512字节的腾跃会见。由于要履历三次cachemiss,CPU才干发明跳读内存的步长是不异的(pos2–pos1==pos3–pos2),尔后假如触发硬件prefetch的话,年夜于512字节的步长大概使得访存操纵很快跨跃page界限,触发prefetch意义已不年夜了。
我们能够持续用后面的程序来察看一下硬件prefetch的体现。将step从1到1024递增,不利用软件prefetch:- [case-1]$./prefetch.emptytest.tar.gz10240arraysize:468787200,step:1024.run...timecost:23.980005,result:6920026783
复制代码 跟着step的慢慢增年夜,能够看出工夫损耗分为三个层次:
step1~16,cost22~25,由于16个int是64byte,恰好在一个cacheline中。这么小的step再加上硬件预取基础上都能cachehit了;
step32~128,cost37~44,这个区间的cost跨度较年夜。在这些step下,单个page内读取值的个数分是32、16、8,硬件prefetch另有必定的余地被触发、并发扬感化。然后跟着可预取数量的削减,cost也不段增添;
step256~,cost64~65,步长凌驾了1024byte,硬件prefetch已不会被触发;
关于TLBcachemiss
使用程序中利用地点的都是假造地点,会见内存时存在假造地点到物理地点的转换历程。转换划定规矩(即页表)是放在内存中的,并由TLB来cache。地点转换必要跳多级页表、屡次读内存,以是假如TLBcachemiss,价值是很年夜的。
不外在我的情况中貌似其实不存在TLBcachemiss的成绩。持续改革程序考证一下:- [case-1]$./prefetch.emptytest.tar.gz10240arraysize:468787200,step:1024.run...timecost:23.980005,result:6920026784
复制代码 改革run函数把全部文件按blocksize分别成多少个块,每一个块独自完成跳读逻辑。
因而,当块对照小时块内跳读时所触及的page对照少,TLB应当能将相干的页表都cache住;而当块对照年夜,大概就会呈现TLBcachemiss。
这内里还存在另外一个成绩,按之前的做法,每一个int跳一次。假如块对照小,第一轮跳读时大概全部块都被cache了,后续的跳读都将cachehit。而块年夜时又没法cache全部块,后续的跳读又将持续cachemiss。这就对察看TLBcachemiss发生很年夜影响。以是程序改成每32个int跳读一次(按后面的了局,跳读32今后功能就欠好了),以疏忽cachehit所带来的影响。
修正main函数,用第4个参数来传送blocksize(0值暗示不分block):- [case-1]$./prefetch.emptytest.tar.gz10240arraysize:468787200,step:1024.run...timecost:23.980005,result:6920026785
复制代码 看起来TLBcachemiss所带来的影响不年夜。
兄弟们,想来你们都看过了昨天的比赛了。我现在的痛苦状跟当时应该差不多。希望本版.net老师不吝赐教,为小弟这一批迷途的羊羔指一条阳光之道!您也知道:学习技术如果只有一个人摸索,那是一件多么痛苦的事情!还有,如果万辛能得名师或长者指点,那又是多么一件幸福和快乐的事情! |
|