P.S. 感谢@ComeIntoPower 提供了宝贵的修改意见。 Part1 寄存器与cache内存的访问是非常慢的,除了内存,还有寄存器(register)、高速缓存cache,它们的访问速度比内存更快。 电脑的存储分级可以用一个非常形象的栗子来说明: 这是一张有两级cache的计算机的存储结构图。 寄存器具有最高的访问速度,在变量前加关键词register即将其加入寄存器。但如图,寄存器的空间是有限的,不应该滥用register,应该仅在访问最频繁的几个变量(如循环变量)前加register。 cache即高速缓存,一般分为3级(有些电脑为两级),访问速度逐级递减。访问变量时,CPU会优先在cache而不是内存中查找,如果cache中不存在此变量,则会进入内存查找,这称为cache miss。如图,内存访问的开销是巨大的,所以cache miss是一个重要的常数问题。 那么如何减少cache miss? 对于cache miss优化,有如下几点: 尽量让某个数组的大小能够卡进cache与register一样,cache的大小同样有限。一些过大的内存是不可以进入cache的。 基数排序时,以256为基数会比256*256更快。因为256大小的四个数组可以轻松进入cache。 详见 洛谷题库 WC2017挑战 Subtask1 保证时空局部性什么是时空局部性? 时间局部性:当一个变量被使用时,它会在短时间内再次被使用。 保证这两样东西的良好有益于减少cache miss。 怎样优化空间局部性?
怎样优化时间局部性?
指令缓存同样是空间局部性的原理,两个相互关联(例如调用对方)的函数应该定义得足够靠近,这能使它们有机会同时被加载到指令cache中。 cache的生活应用:DevC++编译程序后第一遍运行很慢,这是因为编译后的程序没有进入缓存。运行一次后,相关指令进入cache,就会提高运行速度。同样地,对程序进行多次测速求平均时,如果程序访问到了一些大数组,且它们之前没有进入cache,则应该忽略掉第一次运行,取i=2到n次的一段进行平均。 总结:cache优化的原则:紧凑有关联的代码,分离无关联的部分。摘自骆爷pdf的一句话:“这也是编写优美代码的原则。” Part2 指针优化数组连续访问指针用法: for(register int i=1;i<=n;++i)work(a[i]); 这样能够大幅度提高速度。为啥呢? 对于高维数组,例如 ,则a[i][j][k]的访问相对而言十分慢。我们可以将它降成一维数组,a[i∗h∗w+j∗w+k]代替,从而更快地访问。另外也可以利用代数方法减少乘法次数,将其化为a[(i∗h+j)∗w+k]。更高维的数组也可以用这种方法优化。 更多内容请参考: Part3 内联函数、递归、递推与堆栈开销inline函数能够提高效率,因为它能够减少堆栈开销、减少传参耗时。 #define宏函数与inline函数具有相同速度和效果,但会在函数体中反复计算参数,这当参数是一个式子时是很不利的。请读者根据具体情况自行选择。 同样的道理,递推比递归更优秀。它减少了堆栈开销,会更快速,同时避免了爆栈的危险。 Part4 优化STL的动态分配内存一些STL的速度瓶颈即std::allocator对内存的动态分配,这对于push_back等操作不利。 我们可以手写这个struct,用足够大小的内存池来代替动态分配内存。这里我们用派生于std::allocator的myalloc结构体来代替它:
完成后,即可这样定义STL容器: list<int,myalloc<int> > L;vector<double,myalloc<double> > vec; 实测表明,这确实能够优化STL相当一大部分的常数。 由于为了竞赛中方便打,该模板没有编写内存释放函数(网上的模板十分冗长),因此,当内存过大时不要用此模板,太大会RE,不太大也可能变慢。 如果平时想要使用这一类的优化,请参考这个十分详细的内存池教程:https://blog.csdn.net/u010183728/article/details/81531392 |
|