C/C++代码优化的27条建议

这篇文章是极客上阅读量很大的一篇文章,经过博友的翻译,加上个人转载和小改动,以便常常查看,方便之后进行代码优化时拿来作参考。算法

1. 记住阿姆达尔定律

·        funccost是函数func运行时间百分比,funcspeedup是你优化函数的运行的系数。编程

·        因此,若是你优化了函数TriangleIntersect执行40%的运行时间,使它运行快了近两倍,而你的程序会运行快25%。数组

·        这意味着不常用的代码不须要作较多优化考虑(或者彻底不优化)。性能优化

·        这里有句俗语:让常常执行的路径运行更加高效,而运行稀少的路径正确运行。数据结构

2. 代码先保证正确,而后再考虑优化

·        这并不意味着用8周时间写一个全功能的射线追踪算法,而后用8周时间去优化它。多线程

·        分多步来作性能优化。函数

·        先写正确的代码,当你意识到这个函数可能会被常常调用,进行明显的优化。性能

·        而后再寻找算法的瓶颈,并解决(经过优化或者改进算法)。一般,改进算法能显著地改进瓶颈——也许是采用一个你尚未预想到的方法。全部频繁调用的函数,都须要优化。优化

3. 我所了解的那些写出很是高效代码的人说,他们优化代码的时间,是写代码时间的两倍。

4.跳转和分支执行代价高,若是可能,尽可能少用。

·        函数调用须要两次跳转,外加栈内存操做。spa

·        优先使用迭代而不是递归。

·        使用内联函数处理短小的函数来消除函数调用开销。

·        将循环内的函数调用移动到循环外(例如,将for(i=0;i<100;i++) DoSomething();改成DoSomething() for(i=0;i<100;i++) … }})。

·        if…elseif…else if…else if…很长的分支链执行到最后的分支须要不少的跳转。若是可能,将其转换为一个switch声明语句,编译器有时候会将其转换为一个表查询单次跳转。若是switch声明不可行,将最多见的场景放在if分支链的最前面。

5. 仔细思考函数下标的顺序。

·        两阶或更高阶的数组在内存中仍是以一维的方式在存储在内存中,这意味着(对于C/C++数组)array[i][j] 和 array[i][j+1]是相邻的,可是array[i][j] array[i+1][j]可能相距很远。

·        以适当的方式访问存储实际内存中的数据,能够显著地提高你代码的执行效率(有时候能够提高一个数量级甚至更多)。

·        现代处理器从主内存中加载数据处处理器cache,会加载比单个值更多的数据。该操做会获取请求数据和相邻数据(一个cache行大小)的整块数据。这意味着,一旦array[i][j]已经在处理器cache中,array[i][j+1]很大可能也已经在cache中了,而array[i+1][j]可能还在内存中。

6. 使用指令层的并行机制

·        尽管许多程序仍是依赖单线程的执行,现代处理器在单核中也提供了很多的并行性。例如:单个CPU能够同时执行4个浮点数乘,等待4个内存请求并执行一个分支预判。

·        为了最大化利用这种并行性,代码块(在跳转之间的)须要足够的独立指令来容许处理器被充分利用。

·        考虑展开循环来改进这一点。

·        这也是使用内联函数的一个好理由。

7. 避免或减小使用本地变量。

·        本地变量一般都存储在栈上。不过若是数量比较少,它们能够存储在CPU寄存器中。在这种状况下,函数不但获得了更快访问存储在寄存器中的数据的好处,也避免了初始化一个栈帧的开销。

·        不要将大量数据转换为全局变量。

8. 减小函数参数的个数。

·        和减小使用本地变量的理由同样——它们也是存放在栈上。

9. 经过引用传递结构体而不是传值

·        我在射线追踪中还找不到一个场景须要将结构体使用传值方式(包括一些简单结构如:Vector,Point和Color)。

10. 若是你的函数不须要返回值,不要定义一个。

11. 尽可能避免数据转换。

·        整数和浮点数指令一般操做不一样的寄存器,因此转换须要进行一次拷贝操做。

·        短整型(char和short)仍然使用一整个寄存器,而且它们须要被填充为32/64位,而后在存储回内存时须要再次转换为小字节(不过,这个开销必定比一个更大的数据类型的内存开销要多一点)。

12. 定义C++对象时须要注意。

·        使用类初始化而不是使用赋值(Color c(black); Color c; c = black;更快)

13. 使类构造函数尽量轻量。

·        尤为是经常使用的简单类型(好比,color,vector,point等等),这些类常常被复制。

·        这些默认构造函数一般都是在隐式执行的,这或许不是你所指望的。

·        使用类初始化列表(Use Color::Color() : r(0), g(0),b(0) {},而不是初始化函数Color::Color() { r= g =b = 0; } .)

14. 若是能够的话,使用位移操做>>和<<来代替整数乘除法

15. 当心使用表查找函数

·        在其余状况下,查找表会颇有用。对于GPU编程一般优先使用表查找而不是复杂函数。

16. 对大多数类,优先使用+= 、 -= 、 *= 和 /=,而不是使用+ 、 -、 * 、 和?/

·        这些简单操做须要建立一个匿名临时中间变量。

·        例如:Vector v = Vector(1,0,0) + Vector(0,1,0) +Vector(0,0,1);?建立了五个匿名临时Vector: Vector(1,0,0),Vector(0,1,0), Vector(0,0,1), Vector(1,0,0) + Vector(0,1,0), 和 Vector(1,0,0) + Vector(0,1,0) + Vector(0,0,1).

·        对上述代码进行简单转换:Vector v(1,0,0); v+= Vector(0,1,0); v+=Vector(0,0,1);仅仅建立了两个临时Vector: Vector(0,1,0) 和 Vector(0,0,1)。这节约了6次函数调用(3次构造函数和3次析构函数)。

17. 对于基本数据类型,优先使用+?、?-?、?*?、?/?,而不是+=?、?-=?、?*= 和 /=

18. 推迟定义本地变量

·        定义一个对象变量一般须要调用一次函数(构造函数)。

·        若是一个变量只在某些状况下须要(例如在一个if声明语句内),仅在其须要的时候定义,这样,构造函数仅在其被使用的时候调用。

19. 对于对象,使用前缀操做符(++obj),而不是后缀操做符(obj++)

·        使用后缀操做符须要执行一次对象拷贝(这也致使了额外的构造和析构函数调用),而前缀的构造函数不须要一个临时的拷贝。

20. 当心使用模板

·        对不一样的是实例实现进行不一样的优化。

·        标准模板库已经通过良好的优化,不过我建议你在实现一个交互式射线追踪算法时避免使用它。

·        使用本身的实现,你知道它如何使用算法,因此你知道如何最有效的实现它。

·        最重要的是,个人经历告诉我:调试STL库很是低效。一般这也不是一个问题,除非你使用debug版本作性能分析。你会发现STL的构造函数,迭代器和其余一些操做,占用了你15%的运行时间,这会致使你分析性能输出更加费劲。

21. 避免在计算时进行动态内存分配

·        动态内存对于存储场景和运行期间其余数据都颇有用。

·        可是,在许多(大多数)的系统动态内存分配须要获取控制访问分配器的锁。对于多线程应用程序,现实中使用动态内存因为额外的处理器致使了性能降低,由于须要等待分配器锁和释放内存。

·        即使对于单线程应用,在堆上分配内存也比在栈上分配内存开销大得多。操做系统还须要执行一些操做来计算并找到适合尺寸的内存块。

22. 找到你系统内存cache的信息并利用它们

·        若是一个是数据结构正好适合一个cache行,处理整个类从内存中只须要作一次获取操做。

·        确保全部的数据结构都是cache行大小对齐(若是你的数据结构和一个cache行大小都是128字节,仍有可能由于你的结构体中的一个字节在一个cache行中,而其余127字节在另一个cahce行中)。

23. 避免不须要的数据初始化

·        若是你须要初始化一大段的内存,考虑使用memset

24. 尽早结束循环和尽早返回函数调用

25. 在稿纸上简化你的方程式

·        许多方程式中,一般均可以或者在某些条件中取消计算。

·        编译器不能发现这些简化,可是你能够。取消一个内部循环的一些昂贵操做能够抵消你在其余地方的好几天的优化工做。

26. 整数、定点数、32位浮点数和64位双精度数字的数学运算差别,没有你想象的那么大

·        在现代CPU,浮点数运算和整数运算差很少拥有一样的效率。在计算密集型应用(好比射线追踪),这意味这能够忽略整数和浮点数计算的开销差别。这也就是说,你没必要要对算数进行整数处理优化。

·        双精度浮点数运算也不比单精度浮点数运算更慢,尤为是在64位机器上。

27. 不断改进你的数学计算,以消除昂贵的操做

·        sqrt()常常能够被优化掉,尤为是在比较两个值的平方根是否一致时。

·        若是你重复地须要处理 除x 操做,考虑计算1/x的值,乘以它。这在向量规范化(3次除法)运算中赢得了大的改进,不过我最近发现也有点难以肯定的。不过,这仍然有所改进,若是你要进行三次或更多除法运算。

·        若是你在执行一个循环,那些在循环中执行不发生变化的部分,确保提取到循环外部。

·        考虑看看你的计算值是否能够在循环中修改获得(而不每次都从新开始循环计算)。

相关文章
相关标签/搜索