C++ 编程习惯(提高速度)
1.
for(int i = 0 ; i<s.size(); ++i)
for(int i = 0, len = s.size(); i<len; ++i)
第二种避免多次调用size()函数,虽然第一种c++编译时会自动编译成内联函数,但还是第二种好
2.定义变量在循环外面比里面好
在里面定义每次都会重新定义,如果是类的话,每次还都会调用构造函数和析构函数。建议大一点的对象都在类外定义,小的在里面定义(增加可读性)
如果赋值的话就要看逻辑了,这里说的是定义
3.
除法比乘法更慢
4.
内联inline 用于函数短小,反复调用情况
5.
const不仅可以保证变量不被修改,提高稳定性;
同时也让编译器更好地为我们优化代码。const常量编译时就会代替,避免了读其地址浪费时间。
6.
函数形参用const &,避免拷贝
7.
访问容器中的元素,用迭代器比下标访问好
迭代器为访问各种容器提供了统一的方法。
8
迭代器的自增自减,++放前面比后面好
9.
如果有多重if
if(score > 90)
cout<<"优秀"<<endl;
else if(score > 80)
cout<<"良好"<<endl;
else if(score>70)
cout<<"一般"<<endl;
else
cout<<"不及格"<<endl;
若数据量多为不及格,这样效率就低,应该把判断不及格放到最前面
1.整型运行速度快于浮点型,所以能用整型的地方尽量不要用浮点型计算,尤其是除法!移位运算的效率比一般的乘除法效率高!
2.尽量少调用简单函数,例如一个数的平方,直接 a*a,而不要用pow(a,2),pow( )函数的效率很低!
3.容器函数不适合进行数据的插入、删除,但出入栈的效率高;节点函数不适合进行数据的定点访问,但是数据的插入、删除效率高。
4.直接复制的效率高于间接赋值,例如int a(12)高于int a=12(也高于int a;a=12)。
5.双重循环中,最好将大循环放在内层,内存的申请最好放在外面,避免多次重复开辟!重点:多次重复操作是效率低下最为直接的表现!
6.代码尽量简洁(满足功能),结构越复杂效率越低!
7.数据操作最好直接使用内存数据操作(直接操作buffer)。
8.直接使用现有的封装函数很方便,但是效率不是最好的,简单的功能实现,最好还是自己写源码。
9.内存需要用多少就开辟多少,冗余数据会降低效率,也不便于管控内存!
10.尽量避免不同数据类型相互操作,减少数据的类型强转!
11.处理不同事件,需要显著提高效率,可以开启多线程(非常直观有效)!
12.数据在内存上操作的效率是非常高的!尽量避免数据在硬盘上操作,例如,没有特殊要求,不要将内存的数据每次都保存到本地硬盘。
---优化代码,一定要懂数据结构和算法分析!
13.代码前后的防呆很重要!但是过多不必要的防呆(明显不出现的判别式!)会严重降低程序的判定效率。
14.要有良好的“单元测试”习惯,不断优化细小“单元”,提高小模块的效率。
15.熟练使用VS自带的性能分析模块,分析程序中不同代码段的时间分配情况。
16.对于if判断语句,预判为“真”的概率高的语句放在前面,其它根据概率依次放在后面,减少判定的次数。
17.能不用静态变量的,尽量不要用静态变量!可以的话,使用局部变量,也不提倡没有必要的静态局部变量!
18.图像数据的计算,避免直接用OpenCV的数据类型,而使用buffer数据(将mat型转化为unsigned char型)。
19.空间换时间。目前电脑的内存都很大,可以考虑多开辟内存空间,将大模块分散并列计算,最好不要在循环体内重复开辟。
20.for循环内的代码量过大,应该将其拆成多个for循环,这里涉及到了CPU缓存问题!
21.当for循环体系很大时,特别注意循环内的内存开辟问题!能在循环外开辟的内存空间,绝对不能在循环内重复开辟(重复释放),经测试,内外开辟的效率可以相差数十倍!
22.当需要遍历整个Mat类(opencv中矩阵形式)时,切记不要使用 .at< >( , )单点访问的形式!最好是指针的形式 .ptr( ),遇到vector的矩阵向量,最好使用Mat类自带的高阶通道参数 CV_8UC(n),其中n可达到512!,然后采用指针的形式进行数据的访问,效率会大大提高,在我的测试中效率会提高60倍!
23.若平台支持OpenMP,可以采用高度并行的OpenMP进行编程,但是要注意数据的独立性,否则会出现问题!理论上主机有多少核就可以加速几倍!在我的测试中,主机有12核,加速可以达到7倍左右!OpenMP编码有时需要更改代码的结构,看起来比较麻烦,但是对于计算机来说,越是规范的代码,运行效率越高!
24.对于文件流操作,最好用传统的c语言指令,例如,采用fprintf对数据进行输入输出,其速度一般较C++指令快。
25.尽量避免直接使用OpenCV的矩阵乘法,矩阵乘法的效率较低,若矩阵维度较小,可以自行拆成算式计算。在我的测试中,直接乘法计算比矩阵乘法效率高8倍左右!
26.算法调优的小技巧:C代码较C++代码的执行效率高;直接调用成熟的封装库有时还没有自己编写的代码效率高;充分利用计算机的并行计算比单一的串行计算要强很多;越是简单的代码片段并不一定执行效率高,代码要对计算机来说要简洁易懂;调优后的执行效率通常比之前的要提速近百倍!
27.对于速率较高的数据缓存,可以预先开辟内存buffer,数据缓存的时候就直接采用memcpy。一边开辟内存,一边进行缓存可能会遇到数据丢失的情况!
28.向系统申请内存时,要特别注意容量!当程序加大申请内存较多时,要记得随时释放内存。针对系统自行申请的内存(例如std::vector),最好在内存使用完后,自行调用析构函数释放内存(例如,std::vector vec可用 vec.~vector()手动释放 ),而不要等到程序结束后,系统自己去释放,否则累积的内存足以让程序崩溃!注意malloc申请的需要先进行free释放。
29.一般算法程序写好后需要不停地测试优化,为了区别debug模式和release模式,需要在程序中设置判定语句(#if _DEBUG /*测试语句*/ #endif)以示区别不同版本,所有的调试代码均可在debug下运行,而不会出现在release模式下,这样可以非常方便切换模式,而不影响代码结构。
1.将反复使用的数据存放在全局变量里面。
需要重复使用的数据,比如加载的图片,CSV文件等数据,存放在全局变量里面,每次加载DLL时,只加载一次,直到卸载DLL,这些数据一直保持在内存中,避免重复加载,经过测试,这样处理之后,漏装检测的时间由2.5S降低到了1.5S,因为反复读取文件,图片是一个很消耗时间的操作,要尽量避免。
全局变量的使用参考文献:
https://blog.csdn.net/guoqianqian5812/article/details/52397930
2.使用多线程
如果程序中有可以同时进行的代码,譬如五道算术题,大家每人算一道,最后只花费1/5的时间就解决了5道题,多线程就是这个思路,现代的CPU都是多核的,就相当于可以同时算五道题目,一旦你的代码使用了多线程,恐怕你再也回不到单线程的时代了。
有一段程序花费300ms,我们开了32个多线程之后花费16ms,加速了20倍,更别提GPU动辄百倍的加速效果。
多线程参考文献:
https://www.cnblogs.com/wangguchangqing/p/6134635.html
3.用a++和++a,a–,--a
通常使用自加、自减指令和复合赋值表达式(如a-=1及a+=1等)都能够生成高质量的程序代码,编译器通常都能够生成inc和dec之类的指令,而使用a=a+1或a=a-1之类的指令,有很多C编译器都会生成二到三个字节的指令。
4.减少除法运算
无论是整数还是浮点数的运算,除法都会比较耗时,所以最后将除法运算等效成乘法运算。例如:a/b>20可改为a>b*20,可以简化程序的运行时间。
5.尽量减少值传递,多用引用来传递参数。
至于其中的原因,相信大家也很清楚,如果参数是int等语言自定义的类型可能能性能的影响还不是很大,但是如果参数是一个类的对象,那么其效率问题就不言而喻了。例如一个判断两个字符串是否相等的函数,其声明如下:
bool Compare(string s1, string s2)
bool Compare(string *s1, string *s2)
bool Compare(string &s1, string &s2)
bool Compare(const string &s1, const string &s2)
- 1
- 2
- 3
- 4
其中若使用第一个函数(值传递),则在参数传递和函数返回时,需要调用string的构造函数和析构函数两次(即共多调用了四个函数),而其他的三个函数(指针传递和引用传递)则不需要调用这四个函数。因为指针和引用都不会创建新的对象。如果一个构造一个对象和析构一个对象的开销是庞大的,这就是会效率造成一定的影响。
然而在很多人的眼中,指针是一个恶梦,使用指针就意味着错误,那么就使用引用吧!它与使用普通值传递一样方便直观,同时具有指针传递的高效和能力。因为引用是一个变量的别名,对其操作等同于对实际对象操作,所以当你确定在你的函数是不会或不需要变量参数的值时,就大胆地在声明的前面加上一个const吧,就如最后的一个函数声明一样。
同时加上一个const还有一个好处,就是可以对常量进行引用,若不加上const修饰符,引用是不能引用常量的。
举个例子,我们使用值传递和指针传递的方式传递一张10M的图片,使用值传递会多花费0.1S的时间。假如我们使用了10次值传递,将会增加1S的运行时间。
6.循环引发的讨论1(循环内定义,还是循环外定义对象)
请看下面的两段代码:
代码1:
ClassTest CT;
for(inti = 0; i < 100; ++i)
{
CT = a;
//do something
}
- 1
- 2
- 3
- 4
- 5
- 6
代码2:
for(inti = 0; i < 100; ++i)
{
ClassTest CT = a;
//do something
}
- 1
- 2
- 3
- 4
- 5
你会觉得哪段代码的运行效率较高呢?代码1科学家是代码2?其实这种情况下,哪段代码的效率更高是不确定的,或者说是由这个类ClassTest本向决定的,分析如下:
对于代码1:需要调用ClassTest的构造函数1次,赋值操作函数(operator=)100次;对于代码2:需要高用(复制)构造函数100次,析构函数100次。
如果调用赋值操作函数的开销比调用构造函数和析构函数的总开销小,则第一种效率高,否则第二种的效率高。
大部分情况下我们建议循环外定义,也就是方法一
7.循环引发的讨论2(避免过大的循环)
现在请看下面的两段代码,
代码1:
for(inti = 0; i < n; ++i)
{
fun1();
fun2();
}
- 1
- 2
- 3
- 4
- 5
代码2:
for(inti = 0; i < n; ++i)
{
fun1();
}
for(inti = 0; i < n; ++i)
{
fun2();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
注:这里的fun1()和fun2()是没有关联的,即两段代码所产生的结果是一样的。
以代码的层面上来看,似乎是代码1的效率更高,因为毕竟代码1少了n次的自加运算和判断,毕竟自加运算和判断也是需要时间的。但是现实真的是这样吗?
这就要看fun1和fun2这两个函数的规模(或复杂性)了,如果这多个函数的代码语句很少,则代码1的运行效率高一些,但是若fun1和fun2的语句有很多,规模较大,则代码2的运行效率会比代码1显著高得多。可能你不明白这是为什么,要说是为什么这要由计算机的硬件说起。
由于CPU只能从内存在读取数据,而CPU的运算速度远远大于内存,所以为了提高程序的运行速度有效地利用CPU的能力,在内存与CPU之间有一个叫Cache的存储器,它的速度接近CPU。而Cache中的数据是从内存中加载而来的,这个过程需要访问内存,速度较慢。
这里先说说Cache的设计原理,就是时间局部性和空间局部性。时间局部性是指如果一个存储单元被访问,则可能该单元会很快被再次访问,这是因为程序存在着循环。空间局部性是指如果一个储存单元被访问,则该单元邻近的单元也可能很快被访问,这是因为程序中大部分指令是顺序存储、顺序执行的,数据也一般也是以向量、数组、树、表等形式簇聚在一起的。
看到这里你可能已经明白其中的原因了。没错,就是这样!如果fun1和fun2的代码量很大,例如都大于Cache的容量,则在代码1中,就不能充分利用Cache了(由时间局部性和空间局部性可知),因为每循环一次,都要把Cache中的内容踢出,重新从内存中加载另一个函数的代码指令和数据,而代码2则更很好地利用了Cache,利用两个循环语句,每个循环所用到的数据几乎都已加载到Cache中,每次循环都可从Cache中读写数据,访问内存较少,速度较快,理论上来说只需要完全踢出fun1的数据1次即可。
8.局部变量VS静态变量
很多人认为局部变量在使用到时才会在内存中分配,也就是储存单元,而静态变量在程序的一开始便存在于内存中,所以使用静态变量的效率应该比局部变量高,其实这是一个误区,使用局部变量的效率比使用静态变量要高。
这是因为局部变量是存在于堆栈中的,对其空间的分配仅仅是修改一次esp寄存器的内容即可(即使定义一组局部变量也是修改一次)。而局部变量存在于堆栈中最大的好处是,函数能重复使用内存,当一个函数调用完毕时,退出程序堆栈,内存空间被回收,当新的函数被调用时,局部变量又可以重新使用相同的地址。当一块数据被反复读写,其数据会留在CPU的一级缓存(Cache)中,访问速度非常快。而静态变量却不存在于堆栈中。
可以说静态变量是低效的
9.避免使用多重继承
在C++中,支持多继承,即一个子类可以有多个父类。书上都会跟我们说,多重继承的复杂性和使用的困难,并告诫我们不要轻易使用多重继承。其实多重继承并不仅仅使程序和代码变得更加复杂,还会影响程序的运行效率。
这是因为在C++中每个对象都有一个this指针指向对象本身,而C++中类对成员变量的使用是通过this的地址加偏移量来计算的,而在多重继承的情况下,这个计算会变量更加复杂,从而降低程序的运行效率。而为了解决二义性,而使用虚基类的多重继承对效率的影响更为严重,因为其继承关系更加复杂和成员变量所属的父类关系更加复杂。
10.将小粒度函数声明为内联函数(inline)
正如我们所知,调用函数是需要保护现场,为局部变量分配内存,函数结束后还要恢复现场等开销,而内联函数则是把它的代码直接写到调用函数处,所以不需要这些开销,但会使程序的源代码长度变大。
所以若是小粒度的函数,如下面的Max函数,由于不需要调用普通函数的开销,所以可以提高程序的效率。
inline Max(inta, intb)
{
returna>b?a:b;
}
- 1
- 2
- 3
- 4
什么是内联函数
https://blog.csdn.net/zengxiaosen/article/details/73784922
11.多用直接初始化
与直接初始化对应的是复制初始化,什么是直接初始化?什么又是复制初始化?举个简单的例子,
ClassTest ct1;
ClassTest ct2(ct1); //直接初始化
ClassTest ct3 = ct1; //复制初始化
- 1
- 2
- 3
那么直接初始化与复制初始化又有什么不同呢?直接初始化是直接以一个对象来构造另一个对象,如用ct1来构造ct2,复制初始化是先构造一个对象,再把另一个对象值复制给这个对象,如先构造一个对象ct3,再把ct1中的成员变量的值复制给ct3,从这里,可以看出直接初始化的效率更高一点,而且使用直接初始化还是一个好处,就是对于不能进行复制操作的对象,如流对象,是不能使用赋值初始化的,只能进行直接初始化。可能我说得不太清楚,那么下面就引用一下经典吧!
以下是Primer是的原话:
“当用于类类型对象时,初始化的复制形式和直接形式有所不同:直接初始化直接调用与实参匹配的构造函数,复制初始化总是调用复制构造函数。复制初始化首先使用指定构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象”,还有一段这样说,“通常直接初始化和复制初始化仅在低级别优化上存在差异,然而,对于不支持复制的类型,或者使用非explicit构造函数的时候,它们有本质区别:
ifstream file1("filename")://ok:direct initialization
ifstream file2 = "filename";//error:copy constructor is private
- 1
- 2
注:如还对直接初始化和复制初始化有疑问,可以参考一下前面的一篇文章:
C++直接初始化与复制初始化的区别深入解析,里面有有关直接初始化和复制初始化的详细解释。
12.尽量少使用dynamic_cast
dynamic_cast的作用是进行指针或引用的类型转换,dynamic_cast的转换需要目标类型和源对象有一定的关系:继承关系。 实现从子类到基类的指针转换,实际上这种转换是非常低效的,对程序的性能影响也比较大,不可大量使用,而且继承关系越复杂,层次越深,其转换时间开销越大。在程序中应该尽量减少使用。
posted on 2020-09-26 21:19 strangeman 阅读(3491) 评论(0) 编辑 收藏 举报