文章目录
C++性能优化指南
注:以下内容仅限于个人收获总结记录参考
总领:尽量去使用C++所拥有的特性,能提升大部分的性能
- x = x + y和x += y有性能区别,+=的性能提升大大高于前者。
- 如果知道容器所需要的大小,可以提前reserve设置,这样不会出现可能的内存重新分配。较大提升。
- 消除拷贝构造函数的调用,这种最常见的就是std::move和const &xxx了,也会提升。
- 引用变量是作为指针实现的,所以如果需要使用引用里面的元素,务必使用迭代器来访问,这样不用解引用访问。
- 移除对返回值的复制。可以直接传入一个引用,然后在引用上改,最后不用返回值,临时变量什么的。
- 当程序有及其严格的性能需求时,还是需要用C的字符数组来代替std::string,虽然更加难用了,但是性能的提升到顶了,
*ptr++ = str[i]
的使用顺序是ptr++
再用*ptr
去进行赋值。而且还需要手动在末尾进行+"/0"设置。 - 始终记得,优化是有度的,是灵活的,要充分考虑接口的安全性,工作量的增加,后续的代码错误等等
- 我们看到,游戏在debug阶段,非常卡,但是release非常流畅。不仅仅是打开了内联,还有不少release的优化。
- 直接对字符串的+和-对比,append,substr等string类函数的效率往往大于其粗暴的处理。
- earse的使用,标准库的算法使用都是非常不错的。但是注意vector,map等容器的使用earse需要用返回的值赋给迭代器,不然就是造成迭代器不确定。
itor = vec.earse(itor)
,当然map也可以用map.earse(itor++),运用到了后缀++的特性,有一个临时变量铺垫,但是我还是推荐前者。 - 不要混合使用高效的算法,那会使算法时间和复杂度提升。而且,查找算法只有在数据量大的前提下才能有明显的差距,10几个数据看不出什么东西。
- 我们比较的实体,实际上比较的是实体中的某个成员变量值,不是具体的实体比较,实体比较返回的应该永远是false。
- **最近项目上稍微新一点的技术没有使用,所以这里记录一下优化非常不错的智能指针。使用方法大约是:std::unique_ptr<类> 指针名(指向的目标),std::shared_ptr p_forExample(new string(“niconico”),使用API创建就是make_shared和make_unique注意后者需要C++14。创建的api可以同时用来保存被指向的类实例和其引用计数。std::shared_ptr p = std::make_shared(“hello”)**且这个操作是属于比较昂贵的原子操作。如果有主引用,使用unique_ptr
- 初始化和赋值不是一个概念,我们要尽可能用{}初始化,不用new直接初始化(new带有java等其他语言的特性)。
- 在性能极度匮乏的情况下,是不是能用array替换vector。
- 预分配容器容量(这个感觉还行)
- 循环外创建动态变量,综合考虑赋值,构造,拷贝,移动等的性能消耗对比。
- 一般函数的返回值,都是调用的复制构造函数进行复制出来(如果是自定义类)
- 提到了do-white循环和for循环的差异,以前的编译器上性能还是do-white性能好。
- 递减的循环性能比递增的性能好。(为什么?看汇编,会发现递减少一条指令)
- 现代的编译器多会将循环中不变的变量提出后替我们重写循环。要能敏锐的识别出循环中隐藏的函数调用,构造函数,赋值函数,运算符函数,退出作用域时的析构函数,vector等数据结构插入元素时的复制/移动构造函数。
- 不要盲目的去相信一些书上的性能的提升,只有真正的自己性能测试过,才能算。
- 函数的调用过程,代码执行中,遇到函数,先保存当前的执行位置,控制权交给函数体,直到return交回控制权,很高效的讲函数插入到指令执行流中。
- 函数调用中的开销:
最后,栈帧弹出。
25. 成员调用是通过this指针调用的,这个this指针必须写到调用栈上的内存中,或者保存的寄存器中。函数指针有其对应的开销,比如解引用来获取函数体的地址。
26. debug和release版本之间的差别,可能就是内联函数是否有开启。所以inline的性能提升非常大。
27. 虚函数是非常高效的机制,但是要记得,virtual加上重写override,一定要实现,不要空着,结构要舒服。
28. 编译器不会进行我们的数学优化,也不会快速转换常量。
29. >>位移运算符的确能提升性能。但我个人不习惯
30. float和double的快慢,还和编译器有关。
31. 虚函数调用通过索引虚函数表得到,这个开销总是常数时间。
32.
上面的是不正常的(反模式。。是不良的编程),下面的是更多实际中不成熟面向对象代码。
33. 可能世界上根本没有符合标准的实现,C++的标准库也是一样,所以不同实现的STL,也依旧带有不同BUG,虽然他很少。
34. 对于STL来说,性能反而是其次的,特性的覆盖更加重要。
35. 还是那句话,最快的性能,解题速度,往往是单独用来解决这个问题的方法。
36. 对STL的优化实际上是有上限的,如果要突破这个上限,我们就只能修改这个一般性,调用原生函数,移植性换取运行速度。
37. 在黑暗的源代码,库上安全的修改的办法,就是新增一个接口和实现。
38. 继承的层次最好不要超过3层,函数的嵌套调用不能超过3层。
39. 标准库里面有find,find_if和binary_search二分查找。不过这个二分查找返回bool,不返回这个值。
40. vector的直接赋值操作性能很高,因为一步到位。vector只有在往乱七八糟的地方插入元素,空间不够的时候才会有动作。直接完全的insert也高,从一个容器全部复制insert(xx.end(),xx.begin(),xx.end())。仅次于前者。
41. 指针的解引用获取值,消耗的性能比xx.at()和xx[]慢。如果我们写一个循环,去一个个把值复制进去,性能差距比第40条慢6倍。
42. 如果在插入之前知道能总共需要的容量,reserve到需要的大小,再push_back,性能会很好。千万不要在开头插入元素,想办法把第一和最后一个元素的值对调也行,不然性能差距有3000倍之大。
43. deque属于典型的先进先出容器,跟vector类似,不过操作时间比例跟vector对比没有亮点,包括迭代,查找,排序都慢30%左右。对于deque,下标的访问反而比迭代器慢。
44. list的开销对比vector也差多了,而且不支持下标,只能迭代器,find查找。不过对比deque还是有优势的,而且性能与forward_list性能差不多。
45. map的插入,最好带有一个指示的位置迭代器。vector迭代器遍历的效率目前无人能敌。
46. map的插入,先找到想要插入的点,用find,再插,比直接插要快。
47. 散列表的空间申请比较的昂贵。查找性能比有序的vector快1.7倍。但是空间浪费,如果函数不好,负载不行,非常差。
48. 目前,经验丰富的并发开发C++开发人员一直使用的原生线程库或是基本线程服务功能的POSIX线程库(这是一个跨平台解决方案)。C++没有进程概念,只有线程。
49. 单核时代,并发是通过竞争条件,时间分割实现的。
50. STL中对C++的并发支持实际上还是简陋,因为其必须对全平台保持一致性,按照计划,C++17会对并发有突飞猛进的支持。