《性能优化的方法和技巧.docx》由会员分享,可在线阅读,更多相关《性能优化的方法和技巧.docx(21页珍藏版)》请在第一文库网上搜索。
1、性能优化方法和技巧:概述性能优化有三个层次:系统层次算法层次代码层次系统层次关注系统的掌握流程和数据流程,优化主要考虑如何削减消息传递的个数;如何使系统的负载更加均衡;如何充分采用硬件的性能和设施;如何削减系统额外开销(比如上下文切换等)。算法层次关注算法的选择(用更高效的算法替换现有算法,而不转变其接口);现有算法的优化(时间和空间的优化);并发和锁的优化(增加任务的并行性,减小锁的开销);数据结构的设计(比如lock-free的数据结构和算法)。代码层次关注代码优化,主要是cache相关的优化(I-cache, D-cache相关的优化);代码执行挨次的调整;编译优化选项;语言相关的优化技
2、巧等等。性能优化需要相关的工具支持,这些工具包括编译器的支持;CPU的支持;以及集成到代码里面的测量工具等等。这些工具主要目的是测量代码的执行时间以及相关的cache miss, cache hit等数据,这些工具可以关心开发者定位和分析问题。性能优化和性能设计不同。性能设计贯穿于设计,编码,测试的整个环节,是产品生命周期的第一个阶段;而性能优化,通常是在现有系统和代码基础上所做的改进,属于产品生命周期的后续几个阶段(假设产品有多个生命周期)。性能优化不是重新设计,性能优化是以现有的产品和代码为基础的,而不是推倒重来。性能优化的方法和技巧可以指导性能设计,但两者的方法和技巧不能等同。两者关注的
3、对象不同。性能设计是从正向考虑问题:如何设计出高效,高性能的系统;而性能优化是从反向考虑问题:在消失性能问题时,如何定位和优化性能。性能设计考验的是开发者正向建设的力量,而性能优化考验的是开发者反向修复的力量。两者可以互补。后续我会就工具,架构,算法,代码,cache等方面绽开争论这个话题,敬请期盼。评论:优化不能缺少量化的信息,基于量化信息可以解决这些问题(举个例子):什么时候使用静态内存,什么时候使用动态内存?动态与静态的分界线是什么?所谓和谐得恰到好处。最近由于工作需要也在做多核下的应用,这里面性能优化可能是最困难的地方了。个人觉得这方面作为学问共享的话,可以更加注意方法论的描述。优化本
4、身是个仁者见仁智者见智的事情,优化的效果评估一方面取决于你的应用场景,另一方面也取决于你的测试方法。我们就曾为了优化算法而设计了一个数学上看起来很棒的hash,结果在早期应用中我们发觉hash本身带来的cache miss,使得我们还不如使用linear search。总之是特别期盼后续的文章,盼望看到一些新的思路。关于代码层次的优化,大多时候是把高级语言向低级语言去靠。然后就是将系统层次扁平化,厚的层做薄,薄的层去掉。很多时候philosophy的冲突导致实现困难。性能好了,移植性,维护性差了。老板还不兴奋。我现在是越来越不想做这个7舌 了。在做设计阶段,就应当考虑性能的质量属性。假如软件已
5、经成型,再做系统级的性能优化,工作量、风险以及影响都较大。代码级优化:先找热点,然后针对这些热点进行分析,cache使用、削减不必要指令数、削减栈的深度、提高IO总线采用(如IO由每次变成批量访问)、TLBMiss等等。性能是不能一次搞定的,需要建立基线,不停地迭代,不停地确认。性能优化很简洁降低软件的其他质量属性:如维护性、可扩展性。性能优化方法和技巧:代码代码层次的优化是最直接,也是最简洁的,但前提是要对代码很熟识,对系统很熟识。很多事情做到后来,都是一句话:无他,但手熟尔八在绽开这个话题之前,有必要先简洁介绍一下Cache相关的内容,假如对这部分内容不熟识,建议先补补课,做性能优化对Ca
6、che不了解,基本上就是盲人骑瞎马。Cache一般来说,需要关怀以下几个方面1) Cache hierarchyCache的层次,一般有L1,L2, L3 (L是level的意思)的cache。通常来说LI, L2是集成 在CPU里面的(可以称之为On-chip cache),而L3是放在CPU外面(可以称之为Off-chip cache) o当然这个不是确定的,不同CPU的做法可能会不太一样。这里面应当还需要加上register,虽然register不是cache,但是把数据放到register里面是能够提高性能的。2) Cache sizeCache的容量打算了有多少代码和数据可以放到Ca
7、che里面,有了 Cache才有了竞争,才有了替换,才有了优化的空间。假如一个程序的热点(hotspot)已经完全填充了整个Cache,那么再从Cache角度考虑优化就是白费劲气了,巧妇难为无米之炊。我们优化程序的目标是把 程序尽可能放到Cache里面,但是把程序写到能够占满整个Cache还是有肯定难度的,这么大的一个Code path,相应的代码得有多少,代码规律确定是相当的简单(基本上是不行能,至少 我没有见过)。3) Cache line sizeCPU从内存load数据是一次一个cache line;往内存里面写也是一次一个cache line,所以一个cache line里面的数据最
8、好是读写分开,否则就会相互影响。4) Cache associativeCache的关联有全关联(full associative),内存可以映射到任意一个Cacheline;也有Nway关联,这个就是一个哈希表的结构,N就是冲突链的长度,超过了 N,就需要替换。5) Cache type有 I-cache (指令 cache) , D-cache (数据 cache) , TLB (MMU 的cache),每一种又有LI, L2等等,有区分指令和数据的cache,也有不区分指令和数据的cacheo更多与cache相关的学问,可以参考这个链接:dia.org/wiki/CPU_cache或者是
9、附件里面的cache.pdf,里面有一个简洁的总结。代码层次的优化,主要是从以下两个角度考虑问题:1) Icache相关的优化例如精简code path,简化调用关系,削减冗余代码等等。尽量削减不必要的调用。但是有用还是无用,是和应用相关的,所以代码层次的优化很多是针对某个应用或者性能指标的优化。有针对性的优化,更简洁得到可观的结果。2) Dcache相关的优化削减D-cache miss的数量,增加有效的数据访问的数量。这个要比I-cache优化难一些。下面是一个代码优化技巧列表,需要不断地补充,优化和筛选。1) Code adjacency (把相关代码放在一起),推举指数:5颗星把相关代
10、码放在一起有两个涵义,一是相关的源文件要放在一起;二是相关的函数在object文件里面,也应当是相邻的。这样,在可执行文件被加载到内存里面的时候,函数的位置也是相邻的。相邻的函数,冲突(miss?)的几率比较小。而且相关的函数放在一起,也符合模块化编程的要求:那就是-高内聚,低耦合。假如能够把一个code path上的函数编译到一起(需要编译器支持,把相关函数编译到一起),很明显会提高I-cache的命中率,削减冲突。但是一个系统有很多个code path,所以不行能面面俱到。不同的性能指标,在优化的时候可能是冲突的。所以尽量做对所以case都有效的优化,虽然做到这一点比较难。2) Cache
11、 line alignment (cache 对齐),推举指数:4 颗星数据跨越两个cache line,就意味着两次load或者两次store。假如数据结构是cache line对齐的,就有可能削减一次读写。数据结构的首地址cache line对齐,意味着可能有内存铺张(特殊是数组这样连续安排的数据结构),所以需要在空间和时间两方面权衡。3) Branch prediction (分支猜测),推举指数:3颗星代码在内存里面是挨次排列的。对于分支程序来说,假如分支语句之后的代码有更大的执行几率,那么就可以削减跳转,一般CPU都有指令预取功能,这样可以提高指令预取命中的几率。分支猜测用的就是li
12、kely/unlikely这样的宏,一般需要编译器的支持,这样做是静态的分支猜测。现在也有很多CPU支持在CPU内部保存执行过的分支指令的结果(分支指令的cache),所以静态的分支猜测就没有太多的意义。假如分支是有意义的,那么说明任何分支都会执行到,所以在特定状况下,静态分支猜测的结果并没有多好,而且likely/unlikely对代码有很大的侵害(影响可读性),所以一般不 推举使用这个方法。4) Data prefetch (数据预取),推举指数:4颗星指令预取是CPU自动完成的,但是数据预取就是一个有技术含量的工作。数据预取的依据是预取的数据 立刻会用到,这个应当符合空间局部性(spat
13、iallocality),但是如何知道预取的数据会被用到,这个 要看上下文的关系。一般来说,数据预取在循环里面用的比较多,由于循环是最符合空间局部性的代码。但是数据预取的代码本身对程序是有侵害的(影响美观和可读性),而且优化效果不肯定很明显(命中的概率)。数据预取可以填充流水线,避开访问内存的等待,还是有肯定的好处的。5) Memory coloring (内存着色),推举指数:不推举内存着色属于系统层次的优化,在代码优化阶段去考虑内存着色,有点太晚了。所以这个话题可以放到系统层次优化里面去争论。6) Register parameters (寄存器参数),推举指数:4颗星寄存器做为速度最快的
14、内存单元,不好好采用实在是铺张。但是,怎么用?一般来说,函数调用的参数 少于某个数,比如3,参数是通过寄存器传递的(这个要看ABI的商定)。所以,写函数的时候,不要带那么多参数。c语言里还有一个register关键词,不过通常都没什么用处(没试过,不知道效果,不过 可以反汇编看看详细的指令,估量是和编译器相关)。尝试从寄存器里面读取数据,而不是内存。7) Lazy computation (延迟计算),推举指数:5颗星延迟计算的意思是最近用不上的变量,就不要去初始化。通常来说,在函数开头就会初始化很多数据,但是这些数据在函数执行过程中并没有用到(比如一个分支推断,就退出了函数),那么这些动作就
15、是铺张了。变量初始化是一个好的编程习惯,但是在性能优化的时候,有可能就是一个多余的动作,需要综合考虑函数的各个分支,做出打算。延迟计算也可以是系统层次的优化,比如COW(copy-on-write)就是在fork子进程的时候,并没有复制父进程全部的页表,而是只复制指令部分。当有写发生的时候,再复制数据部分,这样可以避开不必要的复制,供应进程创建的速度。8) Early computation (提前计算),推举指数:5颗星有些变量,需要计算一次,多次使用的时候。最好是提前计算一下,保存结果,以后再引用,避开每次都重新计算一次。函数多了,有时就会忽视这个函数都做了些什么,写程序的人可以不了解,但是优化的时候不能不了解。能使用常数的地方,尽量使用常数,加减乘除都会消耗CPU的指令,不行不查。9) Inline or not inline (inline 函数),推举指数:5 颗星Inline or not inline,这是个问题。Inline可以削减函数调用的开销(入栈,出栈的操作),但是inline也有可能造成大量的重复代码,使得代码的体积变大。Inline对debu