C++代码优化
- 多用引用操作,少用指针;
- 使用位运算代替加减乘除取余等计算;
- 用switch代替多层if else;
- 使用内联函数处理代码量小的函数来消除函数调用开销;
- 减少临时对象的使用。
- a+=b的形式效率会高于a=a+b,所以使用时首先考虑使用+= 、 -= 、 *= 和 /=,而不是使用+ 、 - 、 * 、 /。
- 如果可能,对象尽量放在栈上,不要放在堆上,即初始化的时候使用A a(变量1,变量2,...),而不是A a = new(变量1,变量2,...)。
- 尽量使用初始化列表进行初始化工作,例如:A::A() : a(0), b(0), c(0) {},而不是初始化函数A::A() { a= b = c = 0; } 。
- 尽量减少程序的计算量,优化计算过程,简化计算工程;
- 不需要的数据,不要去初始化,初始化大块内存,使用memset。
- 在for语句循环递增变量的时候,使用++i,而不是后置的i++,因为前者不需要返回一个临时对象;
- 减少内存的拷贝操作,减少循环和递归的使用,能用指针替换的绝对不拷贝传递整块内存。
- vector的clear方法并不能把内存回收,要使用swap来回收内存。
- 使用stl容器的时候,建立指针的容器而不是对象的容器,因为拷贝指针很快。
- 函数中传参数的时候,如果是大对象(vector等)尽量传引用,内置类型慎用。
- 如果每次循环迭代都需要对测试条件求值,并且该值的大小不会随着循环的进行而改变时,那么在循环外面计算出该值。
- 针对算法中,某个变量运算每次都一样的,没有必要每次都运算。
- 针对程序中,每次运行都需要重新开辟一大块内存空间,且空间大小一致,可以考虑只开辟一次,之后复用该内存空间。
- 针对内存方面调试推荐工具Valgrind--memcheck,
- 关于继承:不可否认良好的抽象设计可以让程序更清晰,代码更看起来更好,但是她也是有损失的,在继承体系中子类的创建会调用父类的构造函数,销毁时会调用父类的析构函数,这种消耗会随着继承的深度直线上升,所以不要过度的抽象和继承,更为严重的是当多重继承中并且有虚函数的存在时情况更为复杂,的确,这些问题涉及开销,但是,多重继承减少了编码的负担,同时也让问题的解决方案更加简洁,这当然要付出一些代价.总之,与n个基类的多重继承层次相关的额外虚函数表有n-1个。派生类和最左边的非虚基类共享同一个虚函数表。因此,带有2个基类的多重继承层次,有1个(2-1=1)基类的虚函数表和1个派生类的虚函数表(最左边的基类与派生类共享该虚函数表),总共有2个虚函数表,如果有虚继承的存在,会进一步增长这个过程,它是有额外的开销的。
21.对象的复合:对象的复合和继承很相似,当一个对象包含其他对象构造时也会引起额外的构造。关于这点可能会有很多人不解,认为这是不可避免的,举个例子,比如你的类A中包含了类B非指针和引用对象,那么在你构造对象a的时候会自动调用b的无参构造函数,即使你还没有用到她,用指针代替就没有这种消耗,另外如果你的一个对象中用到数组和字符串,你是选择string和vector还是char* 和c系的数组呢,如果没有用到c++stl库提供的相关的高级用法,建议选择后者。
22.构造函数:尽量用参数列表初始化代替参数,避免值传递初始化。
23.变量延时定义:从c系转过来的仍保留着c的习惯,在函数第一行先把所有用到的变量都定义好,但是c是没有运行时的消耗的,对于c++时不一样的,对于c++对象的构造和销毁时有消耗的,如果有大量的对象只在某个if条件的一个分支中出现,那就会有50%的情况这些消耗是可以避免的。对于这点在一个类中也是一样的,如果成员中有成员只在某个时刻能用,就用指针代替,在构造对象时初始化成空指针,避免构造时调用他的构造函数。
24.虚函数:虚函数的底层实现是通过一个虚函数表来实现的,因此有虚函数的类构造时必须先初始化虚函数表,函数调用时也必须先找到虚函数表,然后通过指针偏移找到相应的函数,通常情况下调用虚函数是没有运行时消耗的,但是根据编译器的实现不同,在调用虚函数时,有些调用可能导致增加虚函数表大小的额外开销,或者只有那些需要调整this指针的调用才会发生额外的运行开销,但不会增加虚函数表的大小,在多重继承和虚基类的时候这种消耗会显著增加,关于继承已经提过,所以避免滥用虚函数和虚继承,有时候可以用模版设计来代替虚继承,把运行时的消耗提前到编译期。关于虚函数的消耗:点击打开链接
25.返回值优化: 虽然c++编译器会选择性的进行RVO优化但是不是强制的,当函数有多个返回语句并且返回不通名称的对象,函数过于复杂,返回对象没有定义拷贝构造函数时,rvo优化是不会执行的,所以当函数返回一个很大的对象时在不确定rvo优化会执行时,尽量避免值传递。
26.变量的定义:在定义变量时尽量避免类型的不匹配造成临时变量的产生。
27.内存管理:c++内存管理的大权由我们自己掌握,对于项目中要频繁申请和释放的对象建议用简单的内存池来管理,可以大大的降低频繁申请和释放内存带来的消耗。
28.善用内联:内联函数不仅仅是简单的函数调用似的优化,他还有一个最大的优点就是,可以让编译期进行进行边界代码的运行环境优化,内联把代码拷贝到执行环境处避免了函数调用带来的消耗,并且编译期可以进行正常的编译优化,而函数调用是不能实现的。
29.stl :记住一点stl不是唯一的选择,有时候也不是最好的选择,合理选择stl善用stl算法。
30.缓存:对于多次使用的计算结果及时缓存,避免重复计算。
31.延时计算:对于不关心计算结果的计算过程尽量延时执行或者异步去执行。
31.多线程:尽可能的使用无锁式多线程开发,锁是一个非常消耗性能的东西,保证数据同步的手段有很多,voalite,原子操作都可以实现,尽量通过一些技巧使用这些手段避免所得使用,如果迫不得已要使用锁,尽量减少锁的消耗,比如降低锁的粒度,使用性能更高的锁等等。
32.std::move操作: 当不得不进行深拷贝时,如果深拷贝数据源在拷贝后就不在使用,尽可能的用move操作代替,或者在参数传递时用move操作代替临时的实参变量。
33.cpu缓存:合理的利用cpu cache可以极大的提高代码的运行效率(例如:数组中以每列遍历和每行遍历的效率的不同),当然多线程环境下也要考虑cpu cache带来的影响。
34.内存对齐:在进行网络编程时,最好对网络中传送的数据快进行内存补齐,通常是8字节对其,提高cpu访问内存效率,从而提高数据读写速度。
35.函数参数:用const引用代替值传递,如果函数参数过多,可以用对象来打包参数,减少参数过多带来的性能消耗。
36.算法: 尽可能的优化你的算法。
37.关于智能指针:对于智能指针我的选择是必须用,它可以大大降低程序的crash频率,但是智能指针的和普通指针相比是有额外的消耗的,她的底层是一个原子操作来来统计引用数和一个普通指针,虽然原子操作和锁相比性能高了不少但是和普通的加减操作还是慢了不少,智能指针的大小为16个字节,而普通指针的大小只有4个字节,拷贝的成本也不一样,所以在使用正确的情况下可以使用智能指针的引用来减少拷贝的消耗(注意这里的前提是正确的使用引用,不要引用以一个即将被销毁的变量)。
38.内存池:对于需要频繁申请和释放的内存对象,如果可以重复利用对象的内存,强烈建议通过内存池或者重载对象的new操作符或者重载对象的placement new操作符来减少频繁的申请和释放内存,从而减少申请和释放内存的消耗和内存碎片的产生。
39.其他优化方案:位运算代替乘除法,前缀运算符代替后缀运算等等。