缓存:在现代处理器中,缓存经常与处理器中的数据缓存和指令缓存联系在一起。缓存主要用来存储使用频繁而且代价高昂的计算结果,这样就可以避免对这些结果的重复计算。如,循环内对常量表达式求值是一种常见的低性能问题。

预先计算:预先计算和缓存联系紧密。当缓存某个计算的结果时,需要付出的代价是在对性能有重大影响的关键路径上完成一次计算。如果采用预先计算,那么甚至连这一次计算也可免了。将预先计算放置在影响性能的关键路径之外(例如初始化阶段),就可以避免在性能关键路径上进行代价高昂的计算

降低灵活性:使用堆存储空间(频繁的new/delete)存放IP地址可以改成使用固定大小的局部变量数组代替。

80-20法则:加快常用路径的速度:80-20法则适用于很多场合:80%的程序执行只遍历20%代码,80%的程序运行时间耗费在执行路径的所遇到的20%函数上。80-20法则有力地证明了过于草率的判断是一种错误这个观点。

延迟计算:为一个最终可能不需要的计算付出性能代价显然不是明智之举。然而在复杂的代码中这种情况比比皆是。我们不应该执行”只在某种情况下”才需要的昂贵计算,而应该只在必要的时候执行昂贵计算。这通常意味着把计算推迟到真正需要的时候才进行,因此称之为延迟计算(Lazy Evaluation)。在C++中,对象的定义会调用构造函数和析构函数,这可能是高成本的,因为它导致了立即计算----而这正是我们所要避免的。延迟计算原则建议我们推迟对象的定义,直到要使用该对象时再定义。为不一定用到的构造函数和析构函数付出代价是没有意义的。不仅应该将对象的创建推迟至合适的位置,而且应该直到具备了一个有效创建操作所必需的全部条件后,再创建对象。

无用计算:延迟计算是指那些不总是必须执行的计算,至于哪些计算是必须执行的与程序的执行流程有关,而无用计算是指那些根本无须执行的计算。无论执行流程如何,这些计算结果从不使用,因此它们是完全没有意义的。尽量使用初始化列表方式完成类成员的赋值。

系统体系结构:内存访问的代价差别很大。在某个特定的RISC体系结构中,若数据位于数据缓存中,那么访问它需要耗费一个CPU周期;若位于主存中(缓存失败),则需要8个CPU周期;若位于硬盘上(页面错误),则需要400000个CPU周期。虽然具体的数值会发生变化,但对于不同的处理器体系结构,周期数在总的关系上是一致的:缓存成功、缓存失败和页面错误之间的速度相差多个数量级

当访问数据时,最先搜索的是数据缓存。若数据不在缓存中,硬件产生缓存失败信号,该信号会从RAM或硬盘加载数据至缓存中。缓存以缓存行为单位,通常加载比我们所寻找的特定数据项更大的一块数据。

内存管理动态分配和释放堆内存的代价比较昂贵。从性能角度来讲,使用不需要显式管理的内存所产生的代价要低得多。被定义成局部变量的对象存放于堆栈上。该对象所占用的堆栈空间是为相应函数预留的堆栈空间的一部分,该对象被定义在这个函数范围内。

编译器优化:一个优秀的编译器可以代替开发者实施一些重要的优化,而无须开发者对源代码进行任何干预。第一个想到的优化是寄存器分配。当变量位于寄存器中时,载入和存储该变量是最快的。第二个需要特别注意的优化就会内联。

通常情况下,编译器默认根本不会进行任何优化。这意味着这些重要的性能优化将不会生效----即使在代码中使用了关键字register和inline也无济于事。编译器会自动忽略这些关键字,而且它经常这样做。为了更好地利用这些优化手段,必须通过向命令行添加开关或者在GUI界面上选择性能优化选项,手工打开编译器优化。

编码优化在范围上是局部的,并且不需要对程序的整体设计有深入的理解。当你加入到一个正在进行的开发项目中,并且你对其设计还没有完全理解时,这会是一个很好的起点。

最快的代码是从不执行的代码。试着按照以下步骤去剔除那些代价高昂的计算

(1). 你打算使用该计算结果吗?听起来有点可笑,但这种可笑的事确实会发生----有时我们执行了计算但从未使用计算的结果。

(2). 你现在需要该结果吗?请在真正需要的时候再进行计算。在一些执行流程中有些结果永远不会被使用,因此不必过早地计算。

(3). 你是否已经知道结果?如果在程序执行流程的前期已经计算出了结果,那么应该使用该结果成为可重用的。

有的时候可能无法绕开该计算,此时就必须完成它。那么现在的挑战就是加快计算速度:

(1). 该计算是否过于通用?你的实现只需要跟该领域要求的一样灵活就行,而无须奢求。可以充分利用简化的假设以降低灵活性来增加速度。

(2). 一些灵活性隐藏在函数调用中。通过实现库调用的自定义版本可以提升速度。不过,这些库调用必须是被频繁调用的,否则你的努力将得不到明显效果。熟悉你所使用的库和系统调用中隐藏的代价。

(3). 尽量减少内存管理调用的数量。在绝大多数编译器中,这些调用的代价都是非常高的。

(4). 如果考虑所有可能的输入数据,则可以发现20%的数据在80%时间里出现。因此,应当以牺牲其它不经常出现的场景为代价来提高典型输入的处理速度。

(5). 缓存、RAM和磁盘访问的速度差异很明显。应该多编写缓存友好的代码。

以下是测试代码(coding_optimizations.cpp):

#include "coding_optimizations.hpp"
#include <ctype.h>
#include <string.h>
#include <iostream>
#include <chrono>
#include <string>namespace coding_optimizations_ {// reference: 《提高C++性能的编程技术》:第十三章:编码优化namespace {
static char uppercaseTable[256];void initLookupTable()
{for (int i = 0; i < 256; ++i) {uppercaseTable[i] = toupper(i);}
}class Student1 {
public:// C++保证在Student1的构造函数体执行之前,所有的成员对象已经创建完成,此处即string型的name对象。// 既然我们没有显示告诉编译器如何构造它,编译器就插入了对string默认构造函数的调用。该调用在Student1的构造函数体执行之前进行。Student1(char* nm) { name = nm; }
private:std::string name;
};class Student2 {
public:// 通过在Student2的构造函数初始化列表中显示指明string构造函数,可以避免Student1中的无效计算// 由于我们明确告诉编译器使用哪个string构造函数,编译器将不再隐式地调用string默认构造函数。因此我们实现了一步完成string成员对象的构造Student2(char* nm) : name(nm) {}
private:std::string name;
};} // namespaceint test_coding_optimizations_1()
{initLookupTable();std::chrono::high_resolution_clock::time_point time_start, time_end;const int count{100000000000}, count2{100000};const char* header{"afaIELQEadsfjl943082jdfaadfajqwppreijfadfadfaoueheufiekasdLdamsaldfadfawweevKKA"};int length = strlen(header);char ch;{ // test lowercase letter to uppercase letter: normaltime_start = std::chrono::high_resolution_clock::now();for (int t = 0; t < count; ++t) {for (int i = 0; i < count; ++i) {char* p = const_cast<char*>(header);for (int j = 0; j < length; ++j) {ch = toupper(*p++);//fprintf(stdout, "%c", ch);   }//fprintf(stdout, "\n");}}time_end = std::chrono::high_resolution_clock::now(); fprintf(stdout, "lowercase letter to uppercase letter normal time spend: %f seconds\n",(std::chrono::duration_cast<std::chrono::duration<double>>(time_end-time_start)).count());
}{ // test lowercase letter to uppercase letter: pre-calculatedtime_start = std::chrono::high_resolution_clock::now();for (int t = 0; t < count; ++t) {for (int i = 0; i < count; ++i) {char* p = const_cast<char*>(header);for (int j = 0; j < length; ++j) {ch = uppercaseTable[*p++];//fprintf(stdout, "%c", ch); }//fprintf(stdout, "\n");}}time_end = std::chrono::high_resolution_clock::now(); fprintf(stdout, "lowercase letter to uppercase letter pre-calculated time spend: %f seconds\n",(std::chrono::duration_cast<std::chrono::duration<double>>(time_end-time_start)).count());
}{ // test unuseful calculate: normaltime_start = std::chrono::high_resolution_clock::now();for (int t = 0; t < count2; ++t) {Student1 st("beijing");}time_end = std::chrono::high_resolution_clock::now();   fprintf(stdout, "unuseful calculate normal time spend: %f seconds\n",(std::chrono::duration_cast<std::chrono::duration<double>>(time_end-time_start)).count());
}{ // test unuseful calculate: list inittime_start = std::chrono::high_resolution_clock::now();for (int t = 0; t < count2; ++t) {Student2 st("beijing");}time_end = std::chrono::high_resolution_clock::now();    fprintf(stdout, "unuseful calculate list init time spend: %f seconds\n",(std::chrono::duration_cast<std::chrono::duration<double>>(time_end-time_start)).count());
}return 0;
}} // namespace coding_optimizations_

执行结果如下:

GitHub: https://github.com/fengbingchun/Messy_Test

提高C++性能的编程技术笔记:编码优化+测试代码相关推荐

  1. 提高C++性能的编程技术笔记:总结

    <提高C++性能的编程技术>这本书是2011年出版的,书中有些内容的介绍可能已经过时,已不再适用于现在的C++编程中,但大部分内容还是很有参考意义的. 这里是基于之前所有笔记的简单总结,笔 ...

  2. 提高C++性能的编程技术笔记:设计优化/可扩展性/系统体系结构相关+测试代码

    1. 设计优化 我们可以粗略地将性能优化分为两种类型:编码优化和设计优化.编码优化定义为不需要完整理解要解决的问题或者应用程序的执行流程就能实施的优化.通过定义看出,编码优化用于局部代码,同时该过程不 ...

  3. 提高C++性能的编程技术笔记:虚函数、返回值优化+测试代码

    虚函数:在以下几个方面,虚函数可能会造成性能损失:构造函数必须初始化vptr(虚函数表):虚函数是通过指针间接调用的,所以必须先得到指向虚函数表的指针,然后再获得正确的函数偏移量:内联是在编译时决定的 ...

  4. 提高C++性能的编程技术笔记:引用计数+测试代码

    引用计数(reference counting):基本思想是将销毁对象的职责从客户端代码转移到对象本身.对象跟踪记录自身当前被引用的数目,在引用计数达到零时自行销毁.换句话说,对象不再被使用时自行销毁 ...

  5. 提高C++性能的编程技术笔记:标准模板库+测试代码

    标准模板库(Standard Template Library, STL)是容器和通用算法的强效组合. 渐近复杂度:算法的渐近复杂度是对算法性能的近似估计.它是算法集到特定性能标准集的映射.如果需要对 ...

  6. 提高C++性能的编程技术笔记:内联+测试代码

    内联类似于宏,在调用方法内部展开被调用方法,以此来代替方法的调用.一般来说表达内联意图的方式有两种:一种是在定义方法时添加内联保留字的前缀:另一种是在类的头部声明中定义方法. 虽然内联方法的调用方式和 ...

  7. 提高C++性能的编程技术笔记:多线程内存池+测试代码

    为了使多个线程并发地分配和释放内存,必须在分配器方法中添加互斥锁. 全局内存管理器(通过new()和delete()实现)是通用的,因此它的开销也非常大. 因为单线程内存管理器要比多线程内存管理器快的 ...

  8. 提高C++性能的编程技术笔记:单线程内存池+测试代码

    频繁地分配和回收内存会严重地降低程序的性能.性能降低的原因在于默认的内存管理是通用的.应用程序可能会以某种特定的方式使用内存,并且为不需要的功能付出性能上的代价.通过开发专用的内存管理器可以解决这个问 ...

  9. 提高C++性能的编程技术笔记:临时对象+测试代码

    类型不匹配:一般情况是指当需要X类型的对象时提供的却是其它类型的对象.编译器需要以某种方式将提供的类型转换成要求的X类型.这一过程可能会产生临时对象. 按值传递:创建和销毁临时对象的代价是比较高的.倘 ...

最新文章

  1. React Native学习之 ListView 的简单使用
  2. 分布式系统设计注意点
  3. JAVA解析各种编码密钥对(DER、PEM、openssh公钥)
  4. Linux 命令之 apt-mark -- 对 APT 软件包设置标记
  5. JS检测浏览器是否最大化
  6. 更少的标签,更好的学习,谷歌半监督学习算法FixMatch
  7. 排序 -> 快速排序
  8. deploy owned private docker registry based on docker HUB registry image
  9. VPC中安装Windows Server 2008
  10. 基于python人脸光照不均匀数据的制作
  11. 虚拟串口服务器连接485转网口,串口服务器 串口转以太网 RS232转以太网 485转以太网 串口转网口...
  12. cmd_vel速度话题的使用
  13. rocketmq 启动mqbroker.cmd闪退
  14. python+django+vue的校园新闻网站#毕业设计项目源码
  15. NeuroFluid: 流体仿真的人工智能新范式
  16. OpenHarmony 3.2 Beta Audio——音频渲染
  17. 弱校ACM队员的努力(念-退役的队友跟我们奋斗ACM的理由)
  18. windows8下无线频繁掉线,修复为“默认网关不可用”的解决方案
  19. 回顾社交游戏公司Zynga创业史
  20. 专业级音箱试音10大歌曲

热门文章

  1. C++ 容器1 vector
  2. 物联网协议对比(HTTP、websocket、XMPP、COAP、MQTT和DDS协议)
  3. 用深度神经网络搭建马赛克神器,高清无码效果感人
  4. Ubuntu下安装Anaconda
  5. 学习《Linux设备模型浅析之驱动篇》笔记(一)
  6. linux禁止向指定ip发送数据包,Linux下使用iptables封锁端口禁止邮件发送
  7. python3.7.2怎么用不了pillow_python 3.7.0 下pillow安装方法
  8. vim 键盘宏操作 -- 大道至简
  9. 火焰图(Flame Graphs)的安装和基本用法
  10. Linux下查看Nginx,tomcat等的并发连接数和连接状态