提高C++程序运行效率的10个简单方法
作者:网络转载 发布时间:[ 2015/5/21 13:26:57 ] 推荐标签:开发语言
四、循环引发的讨论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++直接初始化与复制初始化的区别深入解析,里面有有关直接初始化和复制初始化的详细解释。
补充:
这里只是一点点的建议,虽然说了这么多,但是还是要说一下的是:要避免不必要的优化,避免不成熟的优化,不成熟的优化的是错误的来源,因为编译器会为你做很多你所不知道的优化。
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系SPASVO小编(021-61079698-8054),我们将立即处理,马上删除。
相关推荐
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11热门文章
常见的移动App Bug??崩溃的测试用例设计如何用Jmeter做压力测试QC使用说明APP压力测试入门教程移动app测试中的主要问题jenkins+testng+ant+webdriver持续集成测试使用JMeter进行HTTP负载测试Selenium 2.0 WebDriver 使用指南