本文来源于华为云社区《鍗庝负浜戣鍧沖浜戣绠楄鍧沖寮鍙戣呰鍧沖鎶鏈鍧-鍗庝负浜》,作者:毕升小助手。
引言软件开发人员往往希望计算机硬件具有无限的容量、零访问延迟、无限的带宽和低廉的内存,但现实中内存容量越大,访问时间相应地越长。 内存访问速度越快,价格也就越高;带宽越大,价格就越高。 为了解决大容量、高速、低成本的矛盾,根据程序访问的局部原理,将更频繁使用的数据配置在小容量的高速存储器中,分层级联速度不同的多个存储器,协调工作。
图1 memory hierarchy for sever [1]
现代计算机的存储层可以分为几个层。 如图1所示,处理器内部的是寄存器; 在稍远的地方,有一个一级Cache,通常可以容纳64k字节,访问它大约需要1ns。 此外,一级Cache通常分为指令Cache (处理器从指令Cache检索要执行的指令)和数据Cache (处理器从数据Cache取出和取出指令的操作数)。 而且是2级Cache,通常既存储指令又存储数据,容量约为256k,访问它大约需要3-10ns; 而在三级缓存中,容量约为16-64MB,访问它需要约10-20ns; 主存储器、硬盘等紧随其后。 请注意,CPU和Cache通过word传输,Cache通过块(通常为64字节)传输到主存储器。 如上所述,程序的局部性原理一般是指时间的局部性(一定时间内,程序多次访问相同的存储器空间的可能性)和空间的局部性(一定时间内,程序访问附近的存储器空间的可能性),缓存的效率是程序的效率。 例如,当一个程序重复执行循环时,理想情况下,循环的第一次迭代会将代码检索到缓存中,而后续迭代会直接从缓存中检索数据,而无需从主存储器重新加载。 因此,为了提高程序的性能,必须尽可能地在缓存中进行数据访问。 但是,如果在缓存期间数据访问发生冲突,则性能也可能会降低。 由于篇幅的原因,本文重点讨论了编译器在Cache优化中能做什么工作,如果读者对其他内存级别的优化感兴趣,欢迎消息。 以下是一些通过优化Cache的使用来提高程序性能的方法。
对齐和布局现代编译器可以通过调整代码和数据的布局方式来提高Cache命中率和提高程序性能。 本节主要讨论数据和指令的对齐、代码布局对程序性能的影响。 在大多数处理器中,从Cache到主存储器的数据加载是通过Cache line (通常是64字节,有时被称为Cache块)来完成的。 本论文统一使用Cache line。 假设CPU一次只能通过一条缓存线从内存中加载数据,而CPU向内存中写入数据也是一条缓存线,当处理器首次访问数据对象a时,其大小正好为64字节。 如果数据对象a的起始地址不匹配,即数据对象a占用两个不同的Cache line的一部分,则处理器在访问该数据对象时需要两次内存访问,效率低下。 但是,如果数据对象a位于Cache line中,则处理器访问该数据只需要一次内存访问,因此很有效率。 编译器通过合理放置数据对象,避免了不必要的跨越多个Cache line,并尽可能将同一对象组合到一个Cache中,从而有效地使用Cache提高程序性能如果按顺序指定对象,即下一个对象不适合当前Cache line的剩馀部分,请跳过这些剩馀部分,然后从下一个Cache line的开头指定对象,或者选择“相同大小”( size )选项Cache line对齐可能会浪费存储资源,但可能会加快执行速度,如图2所示。 对齐不仅适用于全局静态数据,还适用于分配给堆的数据。 对于全局数据,编译器可以使用汇编语言对齐指令通知链接器。 对于分配给堆的数据,将对象放置在Cache line边界上或最小化对象跨越Cache line的次数是在运行时间内的存储分配器中完成的,而不是在编译器中完成的[2]。
由于图2块的排列,有可能浪费存储区域
如上所述,对齐数据对象可以提高程序性能。 命令Cache的排列也可以提高程序的性能。 此外,代码布局还会影响程序的性能。 通过将频繁执行的基本块的起始地址与Cache line大小倍数的边界对齐,可以增加同时保存在指令Cache中的基本块的数量。 通过将不频繁执行和频繁指令放在不同的Cache line上并优化代码布局来提高程序性能。
利用硬件辅助高速缓存预取是指将内存中的指令和数据提前存储在高速缓存中,从而提高处理器的执行速度。 高速缓存预取可以通过处理器中专用的硬件单元在硬件或软件中执行。 硬件APP读取通过跟踪存储器访问指令数据地址的变化规律,来预测要访问的存储器地址,并将这些数据从主存储器预读取到高速缓存中。 软件APP读取是指在程序中显式插入预读指令,使处理器以无阻塞方式从内存中读取指定的地址数据。 由于硬件APP读取通常无法正常动态关闭,因此大多数情况下软件APP读取和硬件APP读取是共存的,软件APP读取必须与硬件APP读取配合使用才能提高效果。 本文介绍了硬件APP读取关闭后,如何利用软件APP读取来提高性能。 预取指令prefech(x )只是指示硬件开始将地址x的数据从主存储器读取到Cache的提示。 不会导致处理中断,但如果硬件发现异常,则会忽略此预取操作。 如果prefech(x )成功,则如果预取操作失败,意味着下一次取x时会命中Cache,则在下一次读取时可能会发生Cache miss,但不影响程序的准确性[2]。 数据预取如何改变程序的性能? 执行以下步骤:
double a[n]; for(intI=0; i 100; I ) a(I )=0; 假设一条Cache line可以包含两个双精度元素。 第一次访问a[0]时,由于a[0]不在Cache中,因此会发生一次Cache miss,需要从主内存加载到Cache中。 因为一个Cache line可以容纳两个double要素,所以类推访问a时,访问a[2]会发生Cache miss,访问a[3]时不会发生Cache miss,所以程序合计会产生通过软件APP读取等优化,可以减少缓存丢失次数,提高程序性能。 首先,介绍公式[3]。 在上面的表达式中,l是内存等待时间,s是执行一次循环迭代的最短时间。 iterationAhead表示在预取数据到达Cache之前,循环需要多次迭代。 假设根据硬件体系结构计算的iterationAhead=6,则可以按以下方式优化原始程序:
double a[n]; for(intI=0; i 12; I=2//prologueprefetch(a[I]; for(intI=0; i 88; i=2( )/steadystateprefetch ) a[I12]; a[i]=0; a[i 1]=0; }for(intI=88; i 100; //epilogue a[i]=0; 因为预取的数据到达缓存之前,必须在硬件体系结构中循环六次。 展开了prologue和steady state循环,以便可以在一条Cache line中存储两个双精度元素,从而避免浪费prefetch指令。 也就是说,执行prefetch(a[0] )会将a[0],a[1]从主存储加载到Cache中。prologue循环首先执行数组a的前12个元素的预取指令,然后执行steady statary 依次进行上述优化后,在不改变语义的情况下使用预取指令,程序的Cache miss次数从50下降到0,程序的性能将大幅提高。 请注意,预取并不减少从主存储读取数据和缓存数据之间的延迟,而只是通过预取与计算重叠来隐藏此延迟。 也就是说,如果处理器具有预取指令,或者具有可用于预取的非阻塞读取指令,则处理器不能动态地重新排列指令,或者使其小于希望隐藏缓冲器的具体高速缓存延迟预取也不是万能的。 不正确的预取可能会导致缓存冲突,从而降低程序性能。 在考虑预取之前,必须重用数据以减少延迟。 除了软件APP读取外,ARMv8还提供非临时加载/存储命令以提高缓存利用率。 对于某些数据,如果您只需访问一次而不占用Cache,则可以使用此命令进行访问,以避免替换Cache中的重要数据。 例如,在memcpy大数据中,使用此命令对重要的业务有一定的好处。
循环转换和重用Cache中的数据是高效使用Cache的最基本方法。 对于多层嵌套循环( loop interchange )、反转循环迭代执行的顺序( loop reversal )、将两个循环组合为一个循环体( loop fusion ) 环路分割) loop选择合适的环路转换方式可以在保持程序语义的同时改善程序性能。 这些轮转转换的主要目的是优化寄存器、数据缓存和其他存储层的使用。 由于篇幅有限,本节只讨论循环分片如何改善程序性能。 如果您对循环分片感兴趣,请单击。 下一个简单周期:
for(intI=0; i m; I ) for(intj=0; j n; j({x=xa[I]c*b[j] ); }假设数组a、b都是巨大的数组,m、n相等且大,程序不会进行数组越界访问。 如果b[j]在j层循环中跨距过大,则在下次I层循环中复用时数据会从缓存中清除。 这意味着当程序访问b[n-1]时,b[0]、b[1]已经从缓存中清除,此时必须将数据从主存储器重新加载到缓存中,程序的性能将大幅降低如何通过减少Cache miss的次数来提高程序的性能呢? 通过loop tiling循环可以满足我们的期待。 这意味着,通过重新定位循环,数据将分成一个个tile,每个tile的数据将在Cache中被hint [4]。 从内层循环开始tiling,设tile的大小为t,则t远远小于m,n,通过取t的值,在b[t-1]被访问时b[0]仍留在Cache中,Cache miss次数假设n-1正好能被t整除。 此时,b数组的访问顺序如下。
i=1; b[0]、b[1]、b[2].b[t-1]i=2; b[0]、b[1]、b[2].b[t-1].i=n; b[0]、b[1]、b[2]…b[t-1]……I=1; b[n-t]、b[n-t-1]、b[n-t-2].b[n-1]i=2; b[n-t]、b[n-t-1]、b[n-t-2].b[n-1].i=n; b[n-t]、b[n-t-1]、b[n-t-2].b[n-1]经过loop tiling循环变换。
for(intj=0; j n; j=t ( ) for ) intI=0; i m; I ) for(intJJ=j; JJmin(JT,n ); jj ) { x=x a[i] c*b[jj]; }}假设各Cache line可以容纳x个数组元素,loop tiling前a的Cache miss次数为m/X,b的Cache miss次数为m*n/X,合计的Cache miss次数为m*(n 1 )/x loop tiling后的a的Cache miss次数为[n/t]*[m/x],b的Cache miss次数为[t/x]*[n/t]=n/x,总的Cache miss次数为n * [ mt ] 此时,由于n等于m,loop tiling后的Cache miss可以降低约t倍[4]。 如上所述,研究了loop tiling在小的用例中如何提高程序的性能,但是无论如何,对于不同的环路场景,选择合适的环路切换方法在保证程序含义正确的同时
汝之蜜糖,彼之砒霜。 针对不同的硬件,结合具体的硬件架构,利用性能分析工具,通过分析报表和程序,需要从系统层面和算法层面考虑问题,往往会取得意想不到的成果。 本文简要介绍了内存层次优化的几种方法,并结合一些小例子对内存层次优化的知识进行了深入浅出的说明。 最终,我知道这件事需要自己去做。 更多的性能优化知识需要从实践中慢慢探索。
John L. Hennessy,David A. Patterson .计算机体系结构:量化研究方法(第6版) .贾洪峰,译Andrew W.Apple, withjenspalsberg.moderncompilerimplenentationinc http://www.cs.CMU.edu/AFS/cs/academic/class/15745-s19/点击www l20-global-scheduling.pdf https://zhanlan.zhi Hu.com/p/292539074下方了解华为云的新鲜技术。 ~华为云博客_大数据博客_AI博客_云计算_开发者中心-华为云
王者荣耀和小兵互动是七周年的一个活动玩法,想要完成和小兵互动领取加星卡,那么就要按照下面主编提供的方法去
文章导读:现在哪个仙侠手游好?主编今天来给大家推荐一些治愈唯美的手游吧,现在哪个仙侠手游好的推荐,相信有很对
我的世界冰火传说沙蚁螫针剑怎么做很多玩家不知道,冰火传说有很多非常好用的道具武器,沙蚁螫针剑对于其他生物
在《三角战略(TRIANGLE STRATEGY)》中,安娜的定位是刺客/切后排/控制/残局收割,非常强悍的女刺客,可以在单挑敌人
在《三角战略(TRIANGLE STRATEGY)》中,强盗头子的定位是战士/坦克/前排/盗贼,特色偷道具,机动中规中矩,数值方面还
在《三角战略(TRIANGLE STRATEGY)》中,王女-科迪莉亚是第15章选择帮助王子清除残党,特色是拥有诸多强大的恢复技
时间:2023-01-02
时间:2023-01-02
时间:2023-01-02
时间:2023-01-02
时间:2023-01-02
时间:2023-01-02
时间:2023-01-02
时间:2023-01-02
时间:2023-01-02
时间:2023-01-02