学习笔记:C++性能优化指南

C++性能优化指南   

这是一篇关于C++性能优化指南的学习笔记,主要是经过阅读学习Kurt Guntheroth著的Optimized C++:Proven Techniques for Heightened Performance。 这是一本知识量和信息量很大的一本书书,书里详细介绍了影响C++程序性能的缘由,也给出了不少提升性能的优化策略。   
书中不只讲解了软件和系统方面的相关内容,还涉及了计算机的硬件组成的基础知识,使读者能够全面的了解计算机和程序设计。书中介绍的方法是具备通用性的,能够延伸至其余的编程语言,我的认为这是一本能够提高程序设计能力、感觉到优化之美的一本值得一读的好书。
程序员

   

 

1、C++代码优化策略总结

一、用好的编译器并用好编译器(支持C++11的编译器,IntelC++(速度最快)、GNU的C++编译器GCC/G++(很是符合标准),Visual C++(性能折中),clang(最年轻Mac OS x))。
二、使用更好的算法。
三、使用更好的数据结构(不一样的数据结构在使用内存管理器的方式也有所不一样)。
四、使用更好的库(熟悉和掌握标准C++模板库对于进行性能优化的开发员是必须的技能,Boost Project 和 Google Code 公开了不少有用的库)。
五、减小内存分配和复制(减小对内存管理器的调用是一种很是有效的优化手段)。
六、优化内存管理(内存管理器的调度,丰富的API)。
七、移除计算(对于单条的C++语句进行优化)。
八、提升并发性(多个处理核心执行指令)。算法

 

2、影响优化的计算机行为

一、计算机的物理组成自己对计算机性能的限制。
二、计算机的主内存是比较慢的(通往主内存的接口是限制执行速度的瓶颈(冯*诺伊曼瓶颈),(摩尔定理)每一年处理器的核心的数量都会增长,可是计算机的性能未必会提升,由于这些核心只是等待访问内存的机会(内存墙memory wall))。
三、计算机内存的访问方式(并不是以字节为单位),某些内存访问会比其余的更慢(分为一级高速缓存(cache memory)、二级高速缓存、三级高速缓存、主内存、磁盘上的虚拟内存页)。
四、内存的容量是有限的,每一个程序都会与其余程序竞争计算机资源,计算比作决定快。
五、在处理器中,访问内存的性能开销远比其余操做的性能开销大,非对齐访问所须要的时间是全部字节都在同一字节中的两倍。
六、访问频繁使用的内存地址的速度比访问非频繁使用的地址快,访问相邻地址的内存的速度比访问相互远隔的地址的内存块。
七、访问线程间共享的数据比访问非共享的数据资源慢不少。当并发线程共享数据时,同步代码下降了并发量。
八、有些语句隐藏了大量的计算,从语句的外表上看不出语句的性能开销会有多大。编程

 

3、性能测量

一、90/10规则:一个程序会花费90%的运行时去执行10%的代码。
二、只有正确且精确的测量才是准确的测量。
三、分辨率不是准确性。
四、在Windows上,clock()函数提供了可靠的毫秒级的时钟计时功能。在Windows8和以后的版本中,GetSystemTimePreciseAsfileTime()提供了亚微秒的计时功能。
五、计算一条C++语句对内存的读写次数,能够估算出一句C++ 语句的性能开销。数组

 

4、优化字符串的使用

一、因为字符串是动态分配内存的,所以它们的性能开销很是大。它们在表达式中的行为与值相似,它们的实现方式中须要大量的复制。
二、将字符串做为对象而非值能够下降内存分配和复制的频率。
三、为字符串预留内存空间能够减小内存分配的开销。
四、将指向字符串的常量引用传递给函数与传递值的结果几乎同样,可是更加高效。
五、将函数的结果经过输出参数做为引用返回给调用方会复用实参的存储空间,这可能比分配新的存储空间更加高效。
六、即便只是有时候会减小内存分配的开销,仍然是一种优化。缓存

 

5、优化动态分配内存的变量

一、在C++程序中,乱用动态分配内存的变量是最大的“性能杀手”。
二、C++变量(每一个普通数据类型的变量;每一个数组,结构体或类实例)在内存中的布局都是固定的,它们的大小在编译时就已经肯定了。
三、每一个变量都有它的存储期(生命周期),只有在这段时间内变量所占用的存储空间或者内存字节中的值才是有意义的。为变量分配内存的开销取决于存储期(静态存储期、线性局部存储期、自动存储期、动态存储期)。
四、C++变量的全部者决定了变量何时会被建立,何时会被析构(变量全部权是一个单独的概念,与存储期不一样)。动态变量的全部权必须有程序员执行并编写在程序逻辑中,它不受编译器控制,也不禁C++定义。具备强定义全部权的程序会比全部权分散的程序更高效。
五、在C++中,动态变量是由 new 表达式建立,由 delete 表达式释放的。它们会调用C++标准库的内存管理函数。
六、智能指针会经过耦合动态变量的生命周期与拥有该变量的智能指针的生命周期,来实现动态变量全部权的自动化。C++容许多个指针和引用指向同一个动态变量,共享了全部权的动态变量开销更大。
七、静态的建立类成员而且在有必要时采用“两段初始化”,这样能够节省为这些成员变量分配内存的开销。
八、让主指针来拥有动态变量,使用无主指针替代共享全部权。
九、从性能优化的角度上看,使用指针或是引用进行赋值和参数传递,或是返回指针或引用更加高效,由于指针和引用时存储在寄存器中的。
十、当一个数据结构中的元素被存储在连续的存储空间中时,咱们称这个数据结构为扁平的,相比于通用指针连接在一块儿的数据结构,扁平数据结构具备显著的性能优点。性能优化

 

6、优化热点语句

一、除非有一些因素放大了语句的性能开销,不然不值得进行语句级别的性能优化,由于所能带来的性能提高不大。
二、循环中的语句的性能开销被放大的倍数是循环的次数。函数中的语句的性能开销被放大的倍数是函数被调用的次数。被频繁地调用的编程惯用法的性能开销被放大的倍数是其被调用的次数。
三、从循环中移除不变性代码(当代码不依赖于循环的概括变量时,它就具备循环不变性),不过现代编译器很是善于找出循环中被重复计算的具备循环不变性的代码。
四、从循环中移除无谓的函数调用,一次函数调用可能会执行大量指令,这是影响程序性能的一个重要因素,若是一个函数具备循环不变性,那么将它移除到循环外有助于改善性能。有一种函数永远均可以被移动到循环外部,那就是返回值只依赖于函数参数并且没有反作用的纯函数。
五、从循环中移除隐含的函数调用;若是将函数签名从经过值传递实参修改成传递指向类的引用和指针,有时候能够在进行隐式函数调用时移除形参构建。
六、调用函数的开销是很是小的,只是执行函数体的开销可能很是大,若是一个函数被重复调用屡次则累积的开销会变得很大。函数调用的开销主要包括函数调用的基本开销、虚函数的开销、继承中的成员函数调用、函数指针的开销等。函数的调用开销虽然很大,但正由于函数调用才实现了程序的一些复杂的功能。
六、调用操做系统的函数的开销是高成本的。
七、内联函数是一种有效的移除函数调用开销的方法。数据结构


7、使用更好的库  

一、C++为经常使用功能提供了一个简洁的标准库。
*肯定哪些依赖于实现的行为,如每种数据类型的最大值和最小值。
*易于使用可是编写和验证都很繁琐的可移植的超越函数(超越函数指的是变量之间的关系不能用有限次加、减、乘、除、乘方、开方运算表示的函数),如正弦函数和余弦函数、对数函数和幂函数、随机数函数等等。
*除了内存分配外,不依赖于操做系统的可移植的通用数据结构、如字符串、链表和表。
*可移植的通用数据查找算法、数据排序算法和数据转换算法。
*以一种独立于操做系统的方式与操做系统的基础服务相联系的执行内存分配、操做线程、管理和维护时间以及流I/O等任务的函数。 多线程

二、使用C++标准库的注意事项
*标准库的实现中有bug,(标准库和编译器是单独维护的,编译器中也可能存在bug,标准需求的改变、责任的分散、计划问题以及标准库的复杂度都会不可避免地影响它们的质量)。
*标准库的实现可能不符合C++标准,(库的发布计划和编译器是不一样的,而编译器的发布计划与与C++标准不一样,一个标准库的实现可能会领先或是落后于编译器)。
*对于标准库开发人员来讲,性能并不是是最终要的事情,(由于库会被长期使用,因此库的简单性和可维护性更加剧要)。
*库的实现可能会让一些优化手段失效,C++标准库中的有些部分并不是是有用的。
*标准库不如最好的原生函数,(标准库没有为某些操做系统提供异步文件I/O等特性,性能优化人员只能经过调用原生函数,牺牲可移植性来换取运行速度)。并发

三、C++标准库之因此提供这些函数和类,是由于要么没法以其余方式提供这些函数和类,要么这些函数和类被普遍地用于多种操做系统上。在对库进行性能优化时,测试用例很是关键;接口的稳定性是可交付的库的核心。异步

四、扁平继承层次关系(多数抽象都不会有超高三层类继承层次,一旦超高三次可能代表类的层次结构不够清晰,其引入的复杂性会致使性能的降低)。
扁平调用链(绝大多数抽象的实现都不会超高三层嵌套函数的调用,在已经充分解耦的库中是不会包含冗长的嵌套抽象调用链的)。

 

8、优化算法

一、高效的算法是计算机科学一直研究的主题,计算机科学家十分重视算法和数据结构的研究,由于它是展现优化代码的典型事例。当一个程序须要数秒内执行完毕,实际上却要花费数小时时,惟一能够用成功的优化方法可能就是选择一种高效的算法了。算法是一个很是重要且不能简而概之的主题,能够参考《算法导论》,进行更深刻的学习。

二、优化模式
开发人员研究算法和数据结构的缘由之一是其中蕴含着用于改善性能的“思惟库”,这些改善性能的通用技巧是很是的使用的,其中的一些模式也是数据结构、C++语言特性和硬件创新的核心。
* 预计算;能够在程序早期,经过在热点代码前执行执行计算来将计算从热点部分中移除。
* 延迟计算;经过在正真须要执行计算时才执行计算,可将计算从某些代码路径上移除。
* 批量处理;每次对多个元素一块儿进行计算,而不是一次只对一个元素进行计算。
* 缓存;经过保存和复用高代价计算的结果来减小计算量,而不是重复进行计算。
* 特化;经过移除未使用的共性来减小计算量。
* 提升处理量;经过一次处理一大组数据来减小循环处理的开销。
* 提示;经过在代码中加入可能会改善性能的提示来减小计算量。
* 优化期待路径;以期待频率从高到低的顺序对输入数据或是运行时发生的事件进行测试。
* 散列法;计算可变成字符串等大型数据结构的压缩数值映射(散列值)。在进行比较时,用散列代替数据结构能够提升性能。
* 双重检查;经过先进行一项开销不大的检查,而后只在必要时才进行另一项开销昂贵的检查来减小计算量。


9、优化查找和排序

一、改善查找性能的工具箱,测量当前的实现方式的性能来获得比较基准,识别出待优化的抽象活动,将待优化的活动分解为组件算法和数据结构,修改或是替换那些可能并不是最优的算法和数据结构,而后进行性能测试以肯定修改是否有效果。

二、标准库查找算法接受两个迭代器参数:一个指向待查找序列的开始位置,另外一个则指向待查找序列的末尾位置(最后一个元素的下一个位置)。全部的算法还都接受一个要查找的键做为参数以及一个可选的比较函数参数。

三、使用C++标准库优化排序,在可以使用分而治之算法高效地进行查找以前,咱们必须先对序列容器排序,C++标准库提供了两种可以高效地对序列容器进行排序的标准算法——std::sort()和std::stable_sort()。

 

10、优化并发

一、并发是多线程控制的同步执行,并发的目标不是减小指令执行的次数或是每秒访问数据的次数,而是经过提升计算资源的使用率来减小程序运行的时间的。

二、有不少机制可以为程序提供并发,其中有些基于操做系统或是硬件。C++标准库直接支持线程共享内存的并发模型。

三、计算机硬件、操做系统、函数库以及C++自身的特性都可以为程序提供并发支持。

* 时间分隔;这是操做系统的一个调度函数,为每一个程序都分配时间块。操做系统是依赖于处理器和硬件的。它会使用计时器和周期性的中断来调整处理器的调度。
* 虚拟化;虚拟化技术是让操做系统将处理器的时间块分配给客户虚拟机,计算资源可以根据每台客户虚拟机上正在运行的程序的需求进行分配。
* 容器化;容器中包含了程序在检查点的文件系统镜像和内存镜像,其主机是一个操做系统,可以直接提供I/O和系统资源。
* 对称式多处理;是一种包含若干执行相同机器代码并访问相同物理内存的执行单元的计算机,现代多核处理器都是对称式多处理器。使用正真的硬件并发执行多线程控制。
* 同步多线程;有些处理器的硬件核心有两个或多个寄存器集,能够相应地执行两条或多条指令流。最高效第使用软件线程的方法是让软件线程数量与硬件线程数量匹配。
* 多进程;进程是并发的执行流,这些执行流有它们本身的受保护的虚拟内存空间,进程之间经过管道、队列、网路I/O或是其余不共享的机制进行通讯,进程的主要优势是操做系统会隔离各个进程,使其不会互相干扰影响。
* 分布式处理;是指程序活动分布在一组处理器上,这些处理器能够不一样。分布式处理系统一般会被分解为子系统,造成模块化的,易于理解的和可以从新配置的体系结构。
* 线程;线程是进程中的并发执行流,它们之间共享内存;与进程相比,线程的优势在于消耗的资源更少、建立和切换也更快。因为进程中的全部线程都共享相同的内存空间,因此一个线程写入无效的内存地址可能会覆盖掉其余线程的数据结构,致使线程奔溃或是出现不可预测的状况。
* 任务;任务是一个独立线程的上下文中可以被异步调用的执行单元,任务运行的基础是线性池。基于任务的并发构建于线程之上,所以任务也具备线程的优势和缺点。

四、若是没有竞争,那么一个多线程C++程序具备顺序一致性,理想的竞争一块短临界区的核心数量是两个。在临界区中执行I/O操做没法优化性能,可运行线程的数量应当少于或等于处理器核心数量。

 

后记

以上是我关于C++性能优化指南的笔记,主要针对我我的的知识盲点、核心概念要点的简单记录;若有错误,但愿你们批评指正!

相关文章
相关标签/搜索