优化C/C++代码性能的27条建议——<Tips for Optimizing C/C++ Code>译注
2022/5/2 1:14:01
本文主要是介绍优化C/C++代码性能的27条建议——<Tips for Optimizing C/C++ Code>译注,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
layout:post
title: 优化C/C++代码的建议
tags:[C++, Linux, Performan]
优化C/C++代码性能的27条建议——<Tips for Optimizing C/C++ Code>译注
本文来自people.cs.clemson.edu的计算机图形学课程,编号405。关于C++语言,其广泛的应用便是桌面UI和计算机图形学领域。
1.记住Ahmdal法则:
$$
Speedup=\frac {time_{old}}{time_{new}}=\frac {1}{(1-func_{cost})+func_{cost}/func_{speedup}}
$$
$func_{cost}$是函数func被使用时的运行占总程序时间比,$func_{speedup}$是该函数相比原来提升倍率。
假入要优化函数TriangleIntersect(), 有40%的运行时,那么将其运行速率提升一倍后,程序总的运行速率变为原来的1.25倍。
$$
\frac 1{(1-0.4)+0.4/2}=1.25
$$
这意味着不经常使用的代码(infrequently used code)应该被很少去优化。
2.第一次就编码正确,然后才优化它。
这并不意味着要用8周写一个功能完备的光线跟踪器,然后再用8周去优化它。
基于正确编写的代码,然后再清楚被经常使用的函数是哪些,再去优化它。
发现瓶颈,去除瓶颈,通过优化或者提升算法。经常提升算法可以明显消除瓶颈,可能效果出乎意料。对于你经常使用的函数,不断进行优化是个好主意。
3.我知道的写的非常高效的代码的人,都说他们花费了至少两倍的时间在优化代码上。
写作也如此,想写出好的文章,必须要多多修改。
4.跳转/分支代价高,尽可能不使用它们。
函数调用需要两次跳转,额外操纵内存栈。
枚举胜过递归(依然涉及到内存栈多次操作)。
对于短小函数使用内联函数模式(inline Functions)来降低函数开销。
在函数调用内部,改变循环的写法:
把 for(i=0;i<100;i++) do sth 改成 do sth { for(i=0,1<100;1++) {……} } # 以上意思是,执行开销大的动作非必要不要向循环里放,循环中放判断语句。
大量的if...else if...else if..else if... 语句在结束整个判断链条前需要大量跳转到这些case(因为要尝试每种情况)。如果可能的话,把它们换成switch语句,使得编译器可以将之优化为单一跳转的表循环(a table lookup)。如果无法使用switch语句,就把最容易执行的判断语句放到判断链条之前。
5.思考数组参数的顺序。
二维或者更高维的数组仍然存储在一维的内存中。这意味着(对于C/C++数组)Array[i][j]
和Array[i][j+1]
相邻(are adjacent to each other),但是对于Array[i][j]
和Array[i+1][j]
来说它们可能相距很远(may be arbitrary far apart)。
更多或者更少地按顺序方式(fashion)访问被存在内存中的数据,能够动态加速你的代码(有时是一个数量级或者更多)。
Accessing data in a more-or-less sequential fashion, as stored in physical memory, can dramatically speed up your code(sometimes by an order of magnitude,or more)!
当现代CPU从主要内存中加载数据到处理器Cache中时,它们会取出多个值。而它们会取出一块包含被请求的数据和相邻的数据(这一块数据叫cache line).这意味着a[i][j]
之后数据也会在CPU cache中,array[i][j+1]
有很大机会已经在cache中,但是array[i+1][j]
可能仍然只在内存中。
6.考虑指令级并行运算
尽管还能多应用仍然依赖单线程执行,现代的CPU已经有明显大量的单核并行计算能力。这意味着单个CPU可以被模拟执行4个浮点运算,同时等待4个内存请求,并对即将到来的分支进行比较。
大部分并行操作,代码块都需要有足够的依赖结构来使得CPU充分优化。
考虑用展开循环(unrolling loops)提升它。
这也是使用inline内联函数的绝好理由。
7. 避免本地变量的数量
本地变量一般存储在栈中,然而如果没有足够的栈,它们就会被存在寄存器(register)中, 在这种情况下,函数不仅从存储在寄存器中的数据更快地访问你存,而且函数还避免了设置栈帧(stack frame)的过度开销。
永远不要大量(wholesale, 批量,大量)切换到全局变量!
8.减少函数参数的数量
与减少本地变量(local variables,又叫局部变量)的里有一样,它们也被保存在栈中。
9.通过引用(reference)而不是值传递结构(Pass structures)
我所知道的是在光线追踪中没有一个案例是用值传递的(甚至像是简单的案例如向量,指针和色彩)。
值传递涉及拷贝操作
10.如果你不需要一个函数的返回值,那么就不要定义它。
这里是针对性能来说的,一般情况下,性能越好的程序,可读性越差。
11.尽可能尝试避免投射
Try to avoid casting where possible,cast是投射的意思,联系下文,应该是指不要进行强制类型转换
整型和浮点型运算通常会操作不同的寄存器,因此强制类型转换需要拷贝操作。
短整型(char 和short)仍然需要使用整个(full-sized)寄存器,他们需要被扩充成(be padded to 衬垫,向里面加东西)32/64位然后在存入内存前再转回原先的尺寸。(然而,这样的花费等于额外的大数据类型的内存开销。)
12.在声明C++对象变量的时候要小心
使用初始化而不是声明(assignment)一个(例如Color c(black)比Color c;c=black 要快);
13.默认的类构造函数(class constructer)尽可能轻
尤其是经常被操作的简单的常用的类(比如color,vector,point等)。
Particularly for simple,frequently used classes(eg., color,vector, point, etc) that are manipulated frequently.
这些默认的构造函数常在你不知道与不愿意的情况下被调用。
使用构造函数初始化列表。(使用Color::color():r(0),g(0),b(0){}而不是Color::Color(){r=g=b=0}。)
14. 尽可能使用位偏移操作符(shift operation)>>和<<而不是整型的乘法和除法
15.小心使用表循环函数(table-lookup functions)
许多人鼓励在复杂函数中(例如三角函数,trigonometric functions)使用预先计算的值的表。对于光线追踪,这通常是不必要的。内存查找开销是非常(exceedingly)高昂且程度逐步加大的,随着三角函数的冲计算越来越来,从内存中取回值的速度就越快(尤其是当你考虑到trig查找会污染CPU缓存时)。
在其他的实例中,查找表可能非常有用。对于GPU编程,表查找通常比复杂的函数有更好的性能表现。
trig函数是基于表查找的函数
16.对于大多数类,使用操作符+=,-=,*=和/=而不是操作符+,-,和/
简单的操作符需要去创建一个未命名的临时对象。
例如:Vector v= Vector(1,0,0)+Vector(0,1,0)+Vector(0,0,1);创建了五个未命名的临时的Vectors对象: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(1,0,0)。
轻微冗长(verbose)的代码:Vector v(1,0,0); v+= Vector(0,1,0); v+= Vector(0,1,0);只需要创建后面两个临时变量:Vector(0,1,0)和Vector(0,0,1)。
17.针对基础数据类型,使用操作符+,-*和|
而不是+=,-=,*=和|=
18.延迟声明本地变量
声明对象变量总是包含一个函数调用(对于构造函数来说)。
如果一个变量仅仅有时在被需要,则只在必要时候声明,那么构造函数就会只在变量被使用的时候被调用。
19.对于对象obj,使用前缀操作符(++obj)而不是后缀操作符(obj++)
在你的光线跟踪中这可能不会是一个问题。
一个对象的拷贝必须用后缀操作符完成(包括了一个额外的对构造函数和析构函数的调用),但是前缀操作符不需要一个临时的拷贝。
20.小心使用模板(templates)
优化各种示例(instantiation)可能需要不同的办法。
标准模板库已经被优化很好,但是我会避免使用它如果你计划去实现一个可交互的光线追踪(implement an interactive ray tracer)。
为什么?通过自己实现它,你将会知道它运用的算法,所以你将会知道使用代码的最有效方式。
更重要的是,我的经验是对STL库进行DEBUG是缓慢的,一般来说这不是一个问题,除非你要使用调试版本进行评测(expect you will be using debug versions for profiling)。你将会发现STL构造函数,迭代器等,使用了超过15%的运行时间,这些事件会使得读取文件输出更加混乱(confusing)。
21.避免在计算时执行动态内存分配
动态内存用于储存现场事件,其他的数据不会在执行期间改变。
然而,绝大部分系统的动态内存分配需要使用锁来控制访问allocator(access to allocator)。对于使用动态内存的多线程应用,添加额外处理器会导致减速,因为要等待分配内存。
即使对于单线程应用,在堆上分配内存也比在栈上要开销更大。操作系统需要执行一些计算来寻找所需(requisite)尺寸的内存块。
22.找到与使用你的系统内存缓存的信息
如果一个数据结构适应单个cache line, 则著需要从主存提取一次数据即可处理整个类。If a data structure fits in a single cache line, only a single fetch from main memory is required to process the entire class.
确保所有数据结构被排列(be aligned to )到cache line 边界。(如果你的数据结构和cache line都是128位,你的数据结构的1位会占用1个cache line,另外的127位会占用另一个cache line,这会导致更差的性能。)
23.避免不必要的数据初始化
如果初始化一大块内存,考虑使用memset()。
24.尝试早点循环终端和早点函数返回
考虑到光线和三角形(triangle)相交,普遍的例子是,光线将会错过三角形,于是这里应该被优化。
如果你用三角形横断光线,那么你可以立即返回 光线和平面相交是否为负值,这允许你跳过重心坐标计算(barycentric coordinate computation)。这是重大胜利!一旦你知道没有交叉点发生,交叉点函数就会退出。
t value是真值的意思,negetive在这里表示负数,intersect 横断,但是翻译为相交更好
25.简化在纸上的等式
许多的等式中,terms被删除了,尽在一些特殊的案例中。
编译器无法找到这些简化方案,但是你能。在内循环中减少一些开销大的操作能够加速你的程序。
26.整型数,固定点,32位float类型,64位double类型的区别没有你想象的大
在现代CPU中,浮点操作一定与整形操作有相同的吞吐。计算密集型的程序像执行光线追踪 ,这会导致整型与浮点型运算的花费有着可以忽略的差别(this lead to a negligable difference between integer and floating-point costs.) 这意味着,你不应该特意(go out of one's way)使用整形运算。
Double精确(precision)浮点操作可能不会比单精度浮点操作慢,尤其是在64位机器上。我已经看见光线追踪使用全double类型比在相同的机器上使用全float要快。
27. 思考重新组织数学计算来减少高开销操作
sqrt()常常被避免使用,尤其是比较平方值时,比较结果是相同的。
如果你反复除以x(repeatedly divide by x),可以考虑计算1/x并且乘以结果。这曾经是向量规范化(vector normalizations)的一大胜利,不过我最近发现它现在是一个折腾(toss-up)。 然而如果你做超过3次出发,它就仍然是有用的。
如果你操作一个循环,确保不会再枚举之间改变的计算从循环中退出(are pulled out the loop)。
考虑到你是否可以在一个循环中增量地(incrementally)计算值(而不是从头开始对每次迭代进行计算)。
Tips for Optimizing C/C++ Code
Markdown数学公式教程
这篇关于优化C/C++代码性能的27条建议——<Tips for Optimizing C/C++ Code>译注的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-26MATLAB 中 A(7)=[];什么意思?-icode9专业技术文章分享
- 2024-11-26UniApp 中如何实现使用输入法时保持页面列表不动的效果?-icode9专业技术文章分享
- 2024-11-26在 UniApp 中怎么实现输入法弹出时禁止页面向上滚动?-icode9专业技术文章分享
- 2024-11-26WebSocket是什么,怎么使用?-icode9专业技术文章分享
- 2024-11-26页面有多个ref 要动态传入怎么实现?-icode9专业技术文章分享
- 2024-11-26在 UniApp 中实现一个底部输入框的常见方法有哪些?-icode9专业技术文章分享
- 2024-11-26RocketMQ入门指南:搭建与使用全流程详解
- 2024-11-26RocketMQ入门教程:轻松搭建与使用指南
- 2024-11-26手写RocketMQ:从入门到实践的简单教程
- 2024-11-25【机器学习(二)】分类和回归任务-决策树(Decision Tree,DT)算法-Sentosa_DSML社区版