背景
1.做性能优化时,其实也是存在二八定律的,基本上80% 的性能损耗是由20%的代码引起的,而且这20%的代码被基本上被调用的频率非常高。因此一些比较简单的优化就能显著提高性能;
2.在写好代码之后再考虑性能优化的事,否则会出现代码难以读懂,抽象依赖细节等各种问题。另外过早的优化可能没有想象的那么有用,原因参考第一条;
3.在资源不是很紧张的情况下,程序员的生产力的提高比性能的优化更重要。这也是为啥python流行的一个原因吧;
4.很有优化都是在做时间和空间的权衡,考虑哪个更重要,然后决定优化思路;
5.良好的代码风格,代码结构能有效提高代码的性能;
6.优化是有限度的,代码的可读性与可维护性非常重要。
工具
liunx下一般使用gprof,windows下一般使用vs自带的vsperfmon.exe。具体如何使用就不介绍了,参考文章很多。
优化方法
以下介绍的只是我平常使用到的手段,肯定还有很多方法我不知道,希望能有机会学到更多。懒得写代码验证,能找到资料的就直接使用外部资料了。
1.在内存资源不紧张的情况下,使用查表法或者判断可以提高函数运行速度,有兴趣可以看switch的汇编实现。
查表是典型的空间换时间,通过大部分情况下将提前计算好的结果硬编入代码,从而只需要耗费比较的时间就能获取需要通过计算才能得到的结构。
另外对完整表比较大,耗费内存特别多的情况,可以设置部分表,提高计算速度。比如对于电话号码的编码与寻址,将前3或4位作为区号先做一层表,能将查询速度降低3到4个数量级。
2.在操作内存时,要考虑高速缓冲行失效的问题。
What is “cache-friendly” code?
3.尽量使用编译器内建的函数。
编译器内置的函数,一般都是做过优化的,所以一般性能会比较高,比如 __builtin_popcount 。
4.尽量减少大内存对象的构造与析构,可以考虑缓存暂时不用的对象,等待后续继续使用。
这个很好理解,就不举例了,实际上就相当于自己建了一个cache。
5.减少不必要的函数调用。使用inline,减少继承层级等。但是记住,这样的优化是有限度的,否则代码会很难懂。
普通的call指令包括
1)推参与函数返回地址入栈;
2)跳转;
3)保存esp与分配栈帧;
4)平衡堆栈并跳到返回地址。
从上述可以看书,call指令会多执行很多汇编指令。
6.考虑到cpu的指令预测功能,在Linux下使用likely,unlikely宏。要把最容易出现的分支情况放在最前面。
Why is it faster to process a sorted array than an unsorted array?
7.数据存放时需要考虑对齐问题。除非内存紧张或者涉及IPC的情况,一般不用强制对齐。
对齐情况下,CPU寻址的速度会快很多。
8.优化锁的使用,能用原子操作的就不用锁。能用应用层同步手段的,就不要使用内核对象同步。
1)原子操作相对锁来说,速度会快很多。在windows中,CRITICAL_SECTION在可用的情况下,wait函数是不会进入内核的,避免了模式切换,提高了性能。
2)对程序员来说,锁的使用绝对是一个很大的话题。在使用锁时需要注意:只有在必须使用锁时才使用锁;锁住需要锁的区域,不要锁无关的代码区域;锁中最好不要做 IO 等耗时长的操作,尽早退出。
9.优化判断条件,减少循环次数。
找到循环的最小次数,明确各类边界条件,情况不对,直接跳出。
10.优化内存使用,内存块频繁的申请与释放耗时比较大,必要情况下可以自己实现内存管理。
对于内存大小比较相近,频繁申请时可以自己缓冲内存列表,类似 Look aside list。
11.使用c++11右值语义,减少分配与释放临时缓冲区。
c++ 11 move类构造函数相信看过的都很有印象,在对象存在大内存的情况下尤其有用。
12.使用构造函数初始化对象,而不是赋值语句。
c++入门书籍的知识。
13.谨慎使用轮循或者sleep,能用事件通知的情况下,非必要不用考虑轮循。
这个相信大家都明白。
14.充分利用多核优势,拆分非串行化业务。
15.减少非必要的线程创建,节省系统资源与切换造成的性能损耗。
16.将非必要业务从UI线程中挪出,提高界面响应速度,提高用户体验。
17.重构,优化代码结构。将变化源或者算法封装,将代码结构厘清之后,可能优化的效果会好于上述所有的手段。
良好的代码结构能让程序员有效察觉耗时多的代码区块。