优化C/C++代码的小技巧
作者:网络转载 发布时间:[ 2014/9/15 10:47:53 ] 推荐标签:软件开发 C++ 程序
说明:
无意看到一篇小短文,猜测作者应该是一个图形学领域的程序员或专家,介绍了在光线(射线)追踪程序中是如何优化C/C++代码的。倒也有一些参考意义,当然有的地方我并不赞同或者说我也不完全理解,原文在此,我的粗糙翻译如下:
1. 牢记Ahmdal定律
funccost表示是函数func的运行时间百分比,funcspeedup是你优化后函数的运行系数;
所以,如果函数TriangleIntersect()占用40%的运行时间,而在你优化后使它运行快了两倍,那么你的程序运行能够快了25%;
这意味着不经常使用的代码不需要做过多优化(或者完全不优化),比如场景加载过程;
也是:让频繁调用的代码运行得更加高效,而让较少调用的代码保持运行正确;
2. 先有正确的代码,然后再做优化
这并不是说先花8个周时间写一个全功能的光线追踪器,然后再花8个周去优化;
而是在你的管线追踪程序中的多个阶段都进行优化;
如果代码是正确的,而你又知道哪些函数会被频繁的调用,优化是很明显的;
然后找到瓶颈所在,并去除瓶颈(通过优化或者算法改进)。通常来说改进算法可以很显著地优化瓶颈——甚至可能采用了一个你没想到的算法。优化那些你所知道的将被频繁调用的函数是一个很好的做法;
3. 那些我认识的能够写出非常高效的代码的人说,他们花费在优化代码上的时间是他们写代码时间的至少两倍以上
4. 跳转/分支语句是昂贵的,不管何时尽可能的减少使用
函数调用除了栈存储操作外,还需要两次跳转;
优先选择迭代,而不是递归;
如果是短函数,使用内联来消除函数开销;
将循环放在函数内(例如将for(i=0;i<100;i++) DoSomething();改为在DoSomething()内做DoSomething());
长长的if...else if...else if...else if...语句链需要大量的跳转才能结束(除了在测试每个条件时)。如果可能,改为switch语句,有时编译器可以有优化为在一个表中查找和单级跳转。如果switch语句是不可能的,那把经常走到的if语句放在语句链开头;
5. 考虑数组索引的顺序
两维或更多维的数组在内存中仍是按一维存储的。这意思是array[i][j]和 array[i][j+1]是相邻的(C/C++代码),然而array[i][j]和array[i+1][j]却可以相离的任意远;
访问物理内存中的连续数据,可以显著加快你的代码(有时是一个数量级,甚至更多);
现在CPU从主内存中加载数据到高速缓存时,它不仅仅是只加载单一数据,而是加载一块数据,既包含了要请求的数据,也包含部分相邻数据(一个cache行)。这意思是说如果array[i][j]在CPU缓存中,那么array[i][j+1]很有可能也在缓存中了,然而array[i+1][j]可能仍在内存中;
6. 考虑指令级并行性(IPL)
尽管很多程序仍是单线程执行,但现代的CPU已经能够在单核上有显著的并行性。这意味着单CPU也可能同时执行4个浮点数乘法、等待4个内存请求,并执行即将到来的分支比较操作
为了充分利用这种并行性,代码块(比如在跳转语句中)需要足够的独立指令来使CPU得到充分使用;
可以考虑通过展开循环来改进;
这也是使用内联函数的一个很好的原因;
7. 避免或减少局部变量的使用
局部变量通常是存储在栈上。如果很少,可以存储在寄存器中。在这种情况下,函数不仅得到了对存储在寄存器上的数据的更快内存访问的好处,也可以避免建立一个栈帧的开销;
但是,也不要把所有对象都全盘声明为全局变量;
8. 减少函数参数的个数
和减少局部变量的原因一样——他们也是在栈上存储的;
9. 结构体(包括类)传参时使用传引用而不是传值
在光线追踪程序中,哪怕是简单如vector、points、colors等结构,我也没有见过使用值传递的代码
10. 如果你不需要一个函数的返回值,那不要返回
11. 尽可能避免使用转型操作
整数和浮点数的指令集通常在不同的寄存器上运算,因此转型操作需要拷贝操作;
短整形(char和short)仍然需要一个全尺寸的寄存器,而且在存储回内存之前,它们需要对齐到32位或64位上,然后才转换成更小尺寸类型;
12. 当定义C++对象时一定要小心
使用初始化(Color c(black))而不是赋值(Color c, c = black),而前者更快;
13. 使类的默认构造函数尽可能的轻量
特别是那简单的、经常使用的类(例如,颜色,矢量,点等);
这些默认构造函数通常是在你不注意时调用,甚至那时你并不希望这样;
使用构造初始化列表(使用Color::Color() : r(0), g(0), b(0) {}而不是Color::Color() { r = g = b = 0; } );
14. 尽可能使用移位操作符>>和<<,而不是整数乘法和除法
15. 小心使用查表功能
很多人鼓励对于复杂的功能(例如,三角函数)使用预先计算过值的查表法。对于光线跟踪程序来说,这往往是不必要的。内存查找是非常(日益)昂贵的,而且重新计算三角函数往往和从内存中查找值一样快(尤其是当你考虑到内存查找会影响CPU缓存命中率时);
在其它情况下,查表可能是非常有用的。比如在GPU编程中,查表法通常是复杂功能的优先选择;
相关推荐
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11