提高C++性能的编程技术笔记:编码优化+测试代码
缓存:在现代处理器中,缓存经常与处理器中的数据缓存和指令缓存联系在一起。缓存主要用来存储使用频繁而且代价高昂的计算结果,这样就可以避免对这些结果的重复计算。如,循环内对常量表达式求值是一种常见的低性能问题。
预先计算:预先计算和缓存联系紧密。当缓存某个计算的结果时,需要付出的代价是在对性能有重大影响的关键路径上完成一次计算。如果采用预先计算,那么甚至连这一次计算也可免了。将预先计算放置在影响性能的关键路径之外(例如初始化阶段),就可以避免在性能关键路径上进行代价高昂的计算。
降低灵活性:使用堆存储空间(频繁的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
相关文章:

Swift 中使用 SQLite——打开数据库
关于Swift中使用SQLite,接下来可能会分别从打开、增、删、改、查,几个方面来介绍SQLite的具体使用,这一篇重点介绍一下如何打开。 定义全局数据库访问句柄 /// 全局数据库访问句柄 private var db: COpaquePointer nil实现打开数据库函数 …

MVC中获取模型属性的Range和StringLength验证特性设置
MVC中的客户端及服务端模型验证信息都以ModelMetadata类型作为承载,在获得属性的ModelMetadata之后(还不知道怎么获取ModelMetadata的童鞋请自行恶补),我们可以轻松得到一些我们在模型中定义的特性,比如显示名称、是否…

以安装PyTorch为例说明Anaconda在Windows/Linux上的使用
在Windows10上配置完MXNet 1.3.0后,再配置PyTorch 1.0时,发现两者需要依赖的NumPy版本不一致,之前是通过pip安装NumPy,根据pip的版本不同,会安装不同版本的NumPy,使用起来很不方便,而且MXNet和P…

常用 SQL介绍
创建表 /*创建数据表CREATE TABLE 表名 (字段名 类型(INTEGER, REAL, TEXT, BLOB)NOT NULL 不允许为空PRIMARY KEY 主键AUTOINCREMENT 自增长,字段名2 类型,...)注意:在开发中,如果是从 Navicat 粘贴的 SQL,需要自己添加一个指令IF NO…

AttoNets,一种新型的更快、更高效边缘计算神经网络
作者 | Alexander Wong, Zhong Qiu Lin, and Brendan Chwyl 译者 | Rachel 出品 | AI科技大本营(ID:rgznai100)尽管机器学习已经在很多复杂的任务中取得了进展,但现有模型仍然面临许多边缘计算实践的困难,这些边缘计算场景包括移…

Appro DM8127 IPNC 挂载NFS遇到的问题及解决
对于Appro DM8127 IPNC,默认的启动方式是NAND is used for booting kernel and NAND is used as root filesystem 为了调试应用程序方便,通常使用挂载NFS作为 root filesystem 但是如果直接采用ti文档中所给的方法修改文件系统挂载方式(将启动…

提高C++性能的编程技术笔记:设计优化/可扩展性/系统体系结构相关+测试代码
1. 设计优化 我们可以粗略地将性能优化分为两种类型:编码优化和设计优化。编码优化定义为不需要完整理解要解决的问题或者应用程序的执行流程就能实施的优化。通过定义看出,编码优化用于局部代码,同时该过程不牵涉周围的代码。除了这些容易实…

ICLR 2020被爆半数审稿人无相关领域经验,同行评审制度在垮塌?
作者 | 若名出品 | AI科技大本营(ID:rgznai100)根据维基百科,同行评议(peer review),是指由一个或多个具有与作品生产者具有相似能力的人员(同行)对作品进行的评估活动。同行评审方法用于维持质…

Swift 中使用 SQLite——批量更新(事务处理)
本文是Swift 中使用 SQLite系列的收官之作,介绍一下在数据库中的批量更新。 事务 在准备做大规模数据操作前,首先开启一个事务,保存操作前的数据库的状态开始数据操作如果数据操作成功,提交事务,让数据库更新到数据操…

网络管理常用命令之二 - Ipconfig 命令详解(图文)
2、Ipconfig 命令...不带参数.../all 参数.../release 和 /realease6 参数.../Renew 和 /Renew6 参数.../flushdns 参数.../displaydns 参数2、Ipconfig 命令 ipconfig命令也是使用率非常高的一个命令,可用于显示系统的TCP/IP网络配置值,并刷新动态主…

Swift 中使用 SQLite——查询数据
本文主要介绍如何查询 SQLite 结果集,以及封装 SQLite 的操作方法。 准备测试代码 /// 从数据库中加载 person 数组 class func persons() -> [Person]? {// 1. 准备 SQLlet sql "SELECT id, name, age, height FROM T_Person;"// 2. 访问数据库// …

提高C++性能的编程技术笔记:总结
《提高C性能的编程技术》这本书是2011年出版的,书中有些内容的介绍可能已经过时,已不再适用于现在的C编程中,但大部分内容还是很有参考意义的。 这里是基于之前所有笔记的简单总结,笔记列表如下: 跟踪实例࿱…

13岁小孩都跟我抢Python了,完了!
以下来自一位程序员母亲和工作人员的对话。程序员妈妈:您好,可以帮我推荐一本适合我家小孩看的编程书籍吗?兔子:可以的呀,《Scratch从入门到精通》,这本书适合小孩学习,您可以先看一下哦~程序员…

Windows Mobile 6.0 SDK和中文模拟器下载
【转】 Windows Mobile 6.0 SDK和中文模拟器下载 Windows Mobile 6.5 模拟器2010年12月06日 星期一 07:48转载自 zhangyanle86终于编辑 zhangyanle86Windows Mobile 6.0 SDK和中文模拟器下载 SDK 6.0下载页面:http://www.microsoft.com/downloads/details.aspx?fam…

wxPython:Python首选的GUI库 | CSDN博文精选
作者 | 天元浪子来源 | CSDN博客文章目录概述窗口程序的基本框架事件和事件驱动菜单栏/工具栏/状态栏动态布局AUI布局DC绘图定时器和线程后记概述跨平台的GUI工具库,较为有名的当属GTK、Qt 和 wxWidgets 了。GTK是C实现的,由于C语言本身不支持OOP&#x…

Swift 中使用 SQLite——修改和删除数据
本文主要介绍在SQLite中修改数据、删除数据: 更新记录 /// 将当前对象信息更新到数据库 /// /// - returns: 是否成功 func updatePerson() -> Bool {guard let name name else {print("姓名不能为空")return false}if id < 0 {print("id 不…

用python3实现指定目录下文件sha256及文件大小统计
有时会统计某个目录下有哪些文件,每个文件的sha256及文件大小等相关信息,这里用python3写了个脚本用来实现此功能,此脚本可跨平台,同时支持windows和linux,脚本(get_dir_file_info.py)内容如下: import os…

Swift 中使用 SQLite——新增数据
本文重点介绍两个方面,1、新增数据,2、获取自动增长 ID。 建立 Person.swift 数据模型 /// 个人模型 class Person: NSObject {// MARK: - 模型属性/// 代号var id: Int64 0/// 姓名var name: String?/// 年龄var age 0/// 身高var height: Double …

投稿2877篇,EMNLP 2019公布4篇最佳论文
整理 | AI科技大本营(ID:rgznai100)近日,自然语言处理领域的顶级会议之一EMNLP 2019公布了年度最佳论文。EMNLP是由国际语言学会(ACL)下属的SIGDAT小组主办的自然语言处理领域的顶级国际会议,是自然语言算法…

对象检测工具包mmdetection简介、安装及测试代码
mmdetection是商汤和港中文大学联合开源的基于PyTorch的对象检测工具包,属于香港中文大学多媒体实验室open-mmlab项目的一部分。该工具包提供了已公开发表的多种流行的检测组件,通过这些组件的组合可以迅速搭建出各种检测框架。 mmdetection主要特性&am…

(转)eclipse 代码自动补全
转自:http://blog.csdn.net/yushuwai2010/article/details/11856129 一般默认情况下,Eclipse的代码提示功能是比MicrosoftVisualStudio的差很多的,主要是Eclipse本身有很多选项是默认关闭的,要开发者自己去手动配置。如果开发者不…

swift 多线程GCD和延时调用
GCD 是一种非常方便的使用多线程的方式。通过使用 GCD,我们可以在确保尽量简单的语法的前提下进行灵活的多线程编程。在 “复杂必死” 的多线程编程中,保持简单就是避免错误的金科玉律。好消息是在 Swift 中是可以无缝使用 GCD 的 API 的,而且…

目标检测算法Faster R-CNN简介
在博文https://blog.csdn.net/fengbingchun/article/details/87091740 中对Fast R-CNN进行了简单介绍,这里在Fast R-CNN的基础上简单介绍下Faster R-CNN。 目标检测领域从R-CNN开始,通过引入卷积神经网络取得了很多突破性的进展,但是始终未能…

ICCV 2019 | 加一个任务路由让数百个任务同时跑起来,怎么做到?
作者 | Gjorgji Strezoski, Nanne van Noord, Marcel Worring 译者 | 中国海洋大学李杰 出品 | AI科技大本营(ID:rgznai100)摘要传统的多任务(MTL)学习方法依赖于架构调整和大型可训练参数集来联合优化多个任务。但是,…

DEV开发之控件NavBarControl
右键点击RunDesigner弹出如下界面鼠标先点击3或4,1,,然后点击1或2进行相应的新增或删除操作,3是分组,4是项目,4可以直接拖动到相应的分组3.属性caption:显示的名称4.NavBarControl 属性 PaintStyleName绘画风格&…

swift支持多线程操作数据库类库-CoreDataManager
类库方法 获取数据 executeFetchRequest(request:) 同步获取数据 var request: NSFetchRequest NSFetchRequest(entityName: "MonkeyEntity")var myMonkeys:NSArray? CoreDataManager.shared.executeFetchRequest(request)异步获取数据 executeFetchRequest(re…

目标检测(或分隔)算法Mask R-CNN简介
在博文https://blog.csdn.net/fengbingchun/article/details/87195597 中对Faster R-CNN进行了简单介绍,这里在Faster R-CNN的基础上简单介绍下Mask R-CNN。 Mask R-CNN是faster R-CNN的扩展形式,能够有效地检测图像中的目标,并且Mask R-CNN…

未来之城,管理者可能不是人......
大会官网 https://t.csdnimg.cn/KSTh2010 年,IBM 正式提出了“智慧地球”愿景。在 IBM 的设想中,智慧城市应该由六个核心系统组成:组织(人)、业务/政务、交通、通讯、水和能源。(图源 | IBM 官网࿰…

UVa 10701 - Pre, in and post
题目:已知树的前根序,中根序遍历转化成后根序遍历。 分析:递归,DS。依据定义递归求解就可以。 前根序:根,左子树,右子树; 中根序:左子树,根,右子树…

图像集存储成MNIST数据集格式实现
有时会用到将一组图像存放成MNIST中那样的数据格式,以便于用于网络的训练和测试,如MNSIT中的测试集标签t10k-labels.idx1-ubyte和测试集图像t10k-images.idx3-ubyte,各包含了10000个样本,这里以此两个测试集为例详细说明下实现过程…