提高C++程序运行效率的10个简单方法

本文以C/C++程序为例讲述了程序运行效率的10个简单方法,分享给大家供大家参考之用。具体分析如下:

对于每一个程序员来说,程序的运行效率都是一个值得重视,并为之付出努力的问题。但是程序性能的优化也是一门复杂的学问,需要很多的知识,然而并不是每个程序员都具备这样的知识,而且论述如何优化程序提高程序运行效率的书籍也很少。但是这并不等于我们可以忽略程序的运行效率,下面就介绍一下本人积累的一些简单实用的提高程序运行效率的方法,希望对大家有所帮助。

一、尽量减少值传递,多用引用来传递参数。
至于其中的原因,相信大家也很清楚,如果参数是int等语言自定义的类型可能能性能的影响还不是很大,但是如果参数是一个类的对象,那么其效率问题就不言而喻了。例如一个判断两个字符串是否相等的函数,其声明如下:

bool Compare(string s1, string s2)
bool Compare(string *s1, string *s2)
bool Compare(string &s1, string &s2)
bool Compare(const string &s1, const string &s2)

其中若使用第一个函数(值传递),则在参数传递和函数返回时,需要调用string的构造函数和析构函数两次(即共多调用了四个函数),而其他的三个函数(指针传递和引用传递)则不需要调用这四个函数。因为指针和引用都不会创建新的对象。如果一个构造一个对象和析构一个对象的开销是庞大的,这就是会效率造成一定的影响。

然而在很多人的眼中,指针是一个恶梦,使用指针就意味着错误,那么就使用引用吧!它与使用普通值传递一样方便直观,同时具有指针传递的高效和能力。因为引用是一个变量的别名,对其操作等同于对实际对象操作,所以当你确定在你的函数是不会或不需要变量参数的值时,就大胆地在声明的前面加上一个const吧,就如最后的一个函数声明一样。

同时加上一个const还有一个好处,就是可以对常量进行引用,若不加上const修饰符,引用是不能引用常量的。

二、++i和i++引申出的效率问题

看了上面的第一点,你可能觉得,那不就是多调用了四个函数而已,你可能对此不屑一顾。那么来看看下面的例子,应该会让你大吃一惊。

至于整型变量的前加和后加的区别相信大家也是很清楚的。然而在这里我想跟大家谈的却是C++类的运算符重载,为了与整形变量的用法一致,在C++中重载运算符++时一般都会把前加和后加都重载。你可能会说,你在代码中不会重载++运算符,但是你敢说你没有使用过类的++运算符重载吗?迭代器类你总使用过吧!可能到现在你还不是很懂我在说什么,那么就先看看下面的例子吧,是本人为链表写的一个内部迭代器。

_SingleList::Iterator& _SingleList::Iterator::operator++()//前加
{pNote = pNote->pNext;return *this;
}
_SingleList::Iterator _SingleList::Iterator::operator++(int)//后加
{Iterator tmp(*this);pNote = pNote->pNext;return tmp;
}

从后加的实现方式可以知道,对象利用自己创建一个临时对象(自己在函数调用的一个复制),然后改变自己的状态,并返回这个临时对象,而前加的实现方式时,直接改变自己的内部状态,并返回自己的引用。

从第一点的论述可以知道后加实现时会调用复制构造函数,在函数返回时还要调用析构函数,而由于前加实现方式直接改变对象的内部状态,并返回自己的引用,至始至终也没有创建新的对象,所以也就不会调用构造函数和析构函数。

然而更加糟糕的是,迭代器通常是用来遍历容器的,它大多应用在循环中,试想你的链表有100个元素,用下面的两种方式遍历:

for(_SingleList::Iterator it = list.begin(); it != list.end(); ++it)
{//do something
} for(_SingleList::Iterator it = list.begin(); it != list.end(); it++)
{//do something
} 

如果你的习惯不好,写了第二种形式,那么很不幸,做同样的事情,就是因为一个前加和一个后加的区别,你就要调用多200个函数,其对效率的影响可就不可忽视了。

三、循环引发的讨论1(循环内定义,还是循环外定义对象)

请看下面的两段代码:

代码1:

ClassTest CT;
for(int i = 0; i < 100; ++i)
{CT = a;//do something
}

代码2:

for(int i = 0; i < 100; ++i)
{ClassTest CT = a;//do something
}

你会觉得哪段代码的运行效率较高呢?代码1科学家是代码2?其实这种情况下,哪段代码的效率更高是不确定的,或者说是由这个类ClassTest本向决定的,分析如下:

对于代码1:需要调用ClassTest的构造函数1次,赋值操作函数(operator=)100次;对于代码2:需要高用(复制)构造函数100次,析构函数100次。

如果调用赋值操作函数的开销比调用构造函数和析构函数的总开销小,则第一种效率高,否则第二种的效率高。

四、循环引发的讨论2(避免过大的循环)

现在请看下面的两段代码,
代码1:

for(int i = 0; i < n; ++i)
{fun1();fun2();
}

代码2:

for(int i = 0; i < n; ++i)
{fun1();
}
for(int i = 0; i < n; ++i)
{fun2();
}

注:这里的fun1()和fun2()是没有关联的,即两段代码所产生的结果是一样的。

以代码的层面上来看,似乎是代码1的效率更高,因为毕竟代码1少了n次的自加运算和判断,毕竟自加运算和判断也是需要时间的。但是现实真的是这样吗?

这就要看fun1和fun2这两个函数的规模(或复杂性)了,如果这多个函数的代码语句很少,则代码1的运行效率高一些,但是若fun1和fun2的语句有很多,规模较大,则代码2的运行效率会比代码1显著高得多。可能你不明白这是为什么,要说是为什么这要由计算机的硬件说起。

由于CPU只能从内存在读取数据,而CPU的运算速度远远大于内存,所以为了提高程序的运行速度有效地利用CPU的能力,在内存与CPU之间有一个叫Cache的存储器,它的速度接近CPU。而Cache中的数据是从内存中加载而来的,这个过程需要访问内存,速度较慢。

这里先说说Cache的设计原理,就是时间局部性和空间局部性。时间局部性是指如果一个存储单元被访问,则可能该单元会很快被再次访问,这是因为程序存在着循环。空间局部性是指如果一个储存单元被访问,则该单元邻近的单元也可能很快被访问,这是因为程序中大部分指令是顺序存储、顺序执行的,数据也一般也是以向量、数组、树、表等形式簇聚在一起的。

看到这里你可能已经明白其中的原因了。没错,就是这样!如果fun1和fun2的代码量很大,例如都大于Cache的容量,则在代码1中,就不能充分利用Cache了(由时间局部性和空间局部性可知),因为每循环一次,都要把Cache中的内容踢出,重新从内存中加载另一个函数的代码指令和数据,而代码2则更很好地利用了Cache,利用两个循环语句,每个循环所用到的数据几乎都已加载到Cache中,每次循环都可从Cache中读写数据,访问内存较少,速度较快,理论上来说只需要完全踢出fun1的数据1次即可。

五、局部变量VS静态变量

很多人认为局部变量在使用到时才会在内存中分配储存单元,而静态变量在程序的一开始便存在于内存中,所以使用静态变量的效率应该比局部变量高,其实这是一个误区,使用局部变量的效率比使用静态变量要高。

这是因为局部变量是存在于堆栈中的,对其空间的分配仅仅是修改一次esp寄存器的内容即可(即使定义一组局部变量也是修改一次)。而局部变量存在于堆栈中最大的好处是,函数能重复使用内存,当一个函数调用完毕时,退出程序堆栈,内存空间被回收,当新的函数被调用时,局部变量又可以重新使用相同的地址。当一块数据被反复读写,其数据会留在CPU的一级缓存(Cache)中,访问速度非常快。而静态变量却不存在于堆栈中。

可以说静态变量是低效的。

六、避免使用多重继承

在C++中,支持多继承,即一个子类可以有多个父类。书上都会跟我们说,多重继承的复杂性和使用的困难,并告诫我们不要轻易使用多重继承。其实多重继承并不仅仅使程序和代码变得更加复杂,还会影响程序的运行效率。

这是因为在C++中每个对象都有一个this指针指向对象本身,而C++中类对成员变量的使用是通过this的地址加偏移量来计算的,而在多重继承的情况下,这个计算会变量更加复杂,从而降低程序的运行效率。而为了解决二义性,而使用虚基类的多重继承对效率的影响更为严重,因为其继承关系更加复杂和成员变量所属的父类关系更加复杂。

七、尽量少使用dynamic_cast

dynamic_cast的作用是进行指针或引用的类型转换,dynamic_cast的转换需要目标类型和源对象有一定的关系:继承关系。 实现从子类到基类的指针转换,实际上这种转换是非常低效的,对程序的性能影响也比较大,不可大量使用,而且继承关系越复杂,层次越深,其转换时间开销越大。在程序中应该尽量减少使用。

八、减少除法运算的使用

无论是整数还是浮点数运算,除法都是一件运算速度很慢的指令,在计算机中实现除法是比较复杂的。所以要减少除法运算的次数,下面介绍一些简单方法来提高效率:
1、通过数学的方法,把除法变为乘法运算,如if(a > b/c),如果a、b、c都是正数,则可写成if(a*c > b)
2、让编译器有优化的余地,如里你要做的运算是int型的n/8的话,写成(unsigned)n/8有利于编译器的优化。而要让编译器有优化的余地,则除数必须为常数,而这也可以用const修饰一个变量来达到目的。

九、将小粒度函数声明为内联函数(inline)

正如我们所知,调用函数是需要保护现场,为局部变量分配内存,函数结束后还要恢复现场等开销,而内联函数则是把它的代码直接写到调用函数处,所以不需要这些开销,但会使程序的源代码长度变大。

所以若是小粒度的函数,如下面的Max函数,由于不需要调用普通函数的开销,所以可以提高程序的效率。

int Max(int a, int b)
{return a>b?a:b;
}

十、多用直接初始化

与直接初始化对应的是复制初始化,什么是直接初始化?什么又是复制初始化?举个简单的例子,

ClassTest ct1;
ClassTest ct2(ct1);  //直接初始化
ClassTest ct3 = ct1;  //复制初始化

那么直接初始化与复制初始化又有什么不同呢?直接初始化是直接以一个对象来构造另一个对象,如用ct1来构造ct2,复制初始化是先构造一个对象,再把另一个对象值复制给这个对象,如先构造一个对象ct3,再把ct1中的成员变量的值复制给ct3,从这里,可以看出直接初始化的效率更高一点,而且使用直接初始化还是一个好处,就是对于不能进行复制操作的对象,如流对象,是不能使用赋值初始化的,只能进行直接初始化。可能我说得不太清楚,那么下面就引用一下经典吧!

以下是Primer是的原话:

当用于类类型对象时,初始化的复制形式和直接形式有所不同:直接初始化直接调用与实参匹配的构造函数,复制初始化总是调用复制构造函数。复制初始化首先使用指定构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象”,还有一段这样说,“通常直接初始化和复制初始化仅在低级别优化上存在差异,然而,对于不支持复制的类型,或者使用非explicit构造函数的时候,它们有本质区别:
ifstream file1("filename")://ok:direct initialization
ifstream file2 = "filename";//error:copy constructor is private

注:如还对直接初始化和复制初始化有疑问,可以参考一下前面的一篇文章:
C++直接初始化与复制初始化的区别深入解析,里面有有关直接初始化和复制初始化的详细解释。

补充:

这里只是一点点的建议,虽然说了这么多,但是还是要说一下的就是:要避免不必要的优化,避免不成熟的优化,不成熟的优化的是错误的来源,因为编译器会为你做很多你所不知道的优化

希望本文所述对提高大家C++程序设计效率能有所帮助。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/509738.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Kafka JMX 监控 之 jmxtrans + influxdb + grafana

目录 效果图 环境准备 安装 influxdb 安装我们刚刚下载 influxdb rpm文件 查看默认配置 修改参数 启动 influxdb 查看启动状态 设置基本配置 influxdb 其他命令扩展 安装 jmxtrans 可能遇到的异常 验证jmxtrans是否成功运行 安装 Grafana 安装 influxDB 与 Grafa…

如何解决多线程并发访问一个资源的安全性问题?

原子操作&#xff1a;所谓原子操作是指不会被线程调度机制打断的操作&#xff1b;这种操作一旦开始&#xff0c;就一直运行到结束&#xff0c;中间不会有任何 context switch &#xff08;切[1] 换到另一个线程&#xff09;。 关于我对原子操作的理解&#xff1a;原子操作就类…

实例浅析epoll的水平触发和边缘触发,以及边缘触发为什么要使用非阻塞IO

一.基本概念 我们通俗一点讲&#xff1a; Level_triggered(水平触发)&#xff1a;当被监控的文件描述符上有可读写事件发生时&#xff0c;epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如…

Kafka开发指南之 如何Kafka 事务型生产者,保证生产者exactly once

目录 至少一次&#xff08;at least once&#xff09; 最多一次&#xff08;at most once&#xff09; 精确一次&#xff08;exactly once&#xff09; 幂等性 幂等性作用范围 实现方法 代码 事务 事务作用范围 实现方法 代码 我们知道Kafka的消息交付可靠性保障分为…

UML序列图

UML学习&#xff08;三&#xff09;-----序列图 UML的模型中可分为两种&#xff0c;动态模型和静态模型。用例图、类图和对象图都是UML中的静态结构模型。而在UML系统动态模型的其中一种就是交互视图&#xff0c;它描述了执行系统功能的各个角色之间相互传递消息的顺序关系。序…

OpenTSDB 开发指南之 查询数据

前面博主写了一篇文章去介绍opentsdb的http接口的使用方法,但是某一些接口的使用还是比较复杂&#xff0c;这篇文章会通过example来详细讲述opentsdb的一些特性。 本文的举的例子有这些&#xff1a; 基本的写入和查询数据的注释和说明子查询查询中的filters使用查询数据的rat…

OpenTSDB 开发指南之 Api操作数据

/api/put 请求方式&#xff1a;post请求参数&#xff1a; 参数说明examplesummary返回主要摘要/api/put?summarydetails返回详细信息/api/put?detailssync是否同步&#xff0c;即是否等待数据都写入成功后才返回结果/api/put?syncsync_timeout返回结果之前的等待时间/api/p…

xshell下利用SFTP传输文件

SFTP是基于SSH的文件传输协议&#xff0c;与ZMODEM相比具有更加安全且更为快速的文件传输功能。 如何利用SFTP接收文件: 在本地提示以sftp命令登陆拟要接收文件的主机。 Xshell:> sftp hostname在sftp提示下以get命令接收需要的文件。 sftp:/home/user21>get filenam…

libcurl使用方法

原文地址&#xff1a;http://curl.haxx.se/libcurl/c/libcurl-tutorial.html 译者&#xff1a;JGood(http://blog.csdn.net/JGood ) 译者注&#xff1a;这是一篇介绍如何使用libcurl的入门教程。文档不是逐字逐句按原文翻译&#xff0c;而是根据笔者对libcurl的理解&#xff0c…

OpenTSDB 开发指南之 Grafana 展示OpenTSDB监控数据

目录 准备数据 在Grafana创建OpenTSDB连接 创建一个仪表盘 统计 准备数据 将数据插入OpenTSDB {"metric":"jast.data","value":1023,"timestamp":1588742563,"tags":{"type":"jast-graph-data"}}…

linux重启后地址不是之前设置的静态地址的解决方案

按照以下步骤进行安装 1. vi /etc/hosts 修给域名匹配 2. vi /etc/sysconfig/network 修改主机名称 3. vi /etc/sysconfig/network-scripts/ifcfg-eth0 修改主机IP 4. vi /etc/udev/rules.d/70-persistent-net.rules 把里面的eth0删掉&#xff0c;把eth1名称改为eth0

CDH 版本 Kafka 外网设置

登陆CDH页面,进入Kafka配置页面 搜索 advertised 修改advertised.host.name,这里我们有三台Broker,我们把每台的外网ip填写到对应的机器上 advertised.port不填写 我们kafka的端口设置的是9099 将外网端口9099开放,允许外网访问 (这里不做介绍

SQLite、MySQL和PostgreSQL 三种关系数据库比较

关系型数据库的使用已经有相当长的时间了。它们变得流行起来托了管理系统的福&#xff0c;关系模型被实现得相当的好&#xff0c;并且被证明是操作数据的好方法&#xff08;特别是事务性强的应用&#xff09;。 在这篇DigitalOcean文章中&#xff0c;我们将尝试理解一些最常用、…

Ecilpse常用快捷键

Alt &#xff1f; #代码提示ctrl l #去某一行ctrl D #删掉光标所在的一行ctrl shift r #访问某个文件ctrl shiif f #格式化ctrl shift o #包的引入alt 方向键向下 #光标所在行向下移alt 方向键向上 # 光标所在行向上移ctrl alt 方向键向下 #光标所在行向…

OpenTSDB 安装

下载目录 https://github.com/OpenTSDB/opentsdb/releases https://github.com/OpenTSDB/opentsdb/releases/download/v2.4.0/opentsdb-2.4.0.noarch.rpm 安装 GnuPlot yum install gnuplot -y 直接安装OpenTSDB会报错 [rootecs-t-001-0001 openTSDB]# rpm -ivh opentsdb-2.…

leveldb使用指南

这篇文章是levelDB官方文档的译文&#xff0c;原文地址:LevelDB library documentation 这篇文章主要讲leveldb接口使用和注意事项。 leveldb是一个持久型的key-value数据库。key,value可以是任意的字节数组&#xff0c;key之间是有序的。key的比较函数可以由用户指定。 1. 打…

java中同步锁的原理和实现

接口 Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构&#xff0c;可以具有差别很大的属性&#xff0c;可以支持多个相关的 Condition 对象。 锁是控制多个线程对共享资源进行访问的工具。通常&#xff0c;锁提供了对共享资…

HugeGraph 配置参数

gremlin-server.yaml 参数名称参数说明默认值scriptEvaluationTimeout查询超时时间&#xff0c;单位毫秒30000rest-server.properties 参数名称参数说明默认值restserver.max_worker_threads服务器最大工作线程2*cpurestserver.min_free_memory当服务器内存小于该值时&#x…

本地yum仓库以及网络版yum的私有仓库详细的安装配置

本地仓的配置 第一步&#xff1a;开启CD/DVD 设备&#xff0c;并且把centos镜像链接添加至设备中 第二步&#xff1a;创建一个 文件夹用来挂在 光驱文件 mkdir /mnt/cdrom &#xff08;通常约定挂载在/mnt目录下 &#xff09; 第三步&#xff1a;mount -t iso9660 -o ro /de…

LevelDB简述

转自&#xff1a;http://www.cnblogs.com/melons/p/5791855.html 既然开始了&#xff0c;哪有停下的道理&#xff0c;先了解一下Levedb的关荣历史、完美现在和光辉的未来&#xff1a; Leveldb: 1.Leveldb是一个google实现的非常高效的kv数据库&#xff0c;目前能够支持billion级…