C++程序运行效率的思考
在做LeetCode435题时,需要对二维vector数组进行排序,使用lambda表达式一直超时,后来发现并不是lambda表达式的问题,而是没用使用引用传递。故记录一下对C++程序的运行效率的思考。
指针传递、引用传递效率优于值传递
值传递在参数传递和函数返回时,需要调用构造函数和析构函数(既多调用了四个函数),而由于指针传递和引用传递不会创建新的对象,所以不需要调用构造函数和析构函数。如果参数是个类对象,那么调用构造函数和析构函数则会使效率很低。
bool cmp(string a, string b); // 值传递
bool cmp(string *a, string *b); //指针传递
bool cmp(string &a, string &b); //引用传递
bool cmp(const string &a, const string &b);
引用是一个变量的别名,对其操作等同于对实际对象操作。加上const,即可以对常量进行引用,若不加const,引用不能引用常量。
++i效率优于i++
现在在编译器进行优化之后,两者在简单应用时,效率基本相当。但是,两者在某些情况下仍然有所不同。
i++用临时对象保存原来的对象,然后原对象自增,再返回临时对象,不能作为左值。++i直接用原对象进行自增,然后返回原对象的引用,可以作为左值。
由于要生成临时对象,i++需要调用两次拷贝构造函数和析构函数(原对象赋值给临时对象一次,临时对象最后以值传递的方式返回一次)。++i由于不用生成临时变量,且返回的是原对象的引用,故不存在调用析构函数和构造函数的开销,所以效率更高。
循环内定义vs循环外定义
// code 1
Class A;
for(int i = 0; i < 10; i++)
A = B;
// code 2
for(int i = 0; i < 10; i++)
Class A = B;
结论是:哪段代码的效率高是不确定的。
对于第一段代码,需要调用A的构造函数1次,赋值操作函数10次。对于第二段代码,需要调用复制构造函数10次,析构函数10次。需要对其中具体的函数所需的时间进行对比。
一个循环vs两个循环
// code 1
for(int i = 0; i < 10; i++) {
function1();
function2();
}
// code 2
for(int i = 0; i < 10; i++)
function1();
for(int i = 0; i < 10; i++)
function2();
虽然第一段代码比第二段代码少了10次的自加和判断运算,但事实上,主要还是看function1和function2这两段代码的**规模(复杂性)**如何了。如果这两个函数的语句很少,则第一段代码的规模更高;如果语句较多,规模较大,则第二段代码的运行效率更高。
原因在于cache的设计原理涉及时间局部性和空间局部性。
- 时间局部性,即如果一个存储单元被访问,则可能该单元很快会被再次访问(因为程序存在循环)。
- 空间局部性,即如果一个存储单元被访问,则该单元邻近的单元很快也可能被访问(因为程序中大部分指令是顺序存储执行的,数据一般也是连续存储的)。
故,如果function1和function2的语句很多,假设都大于cache的容量,则第一段代码每次都是先将function1加载到cache中,然后踢出,再将function2加载到cache中,再踢出。如此往复。从cache向内存中加载数据的操作,比较费时。而第二段代码,理论上只需要将function1的代码踢出去一次即可。
局部变量效率优于静态变量
局部变量在使用时才会在内存中给它分配存储单元,静态变量从一开始就存在于内存中。
局部变量存储在堆栈中,其最大的好处是,函数能够重复使用内存,当一个函数调用完毕,退出程序堆栈,内存空间被回收。如果有新的函数将要被调用,局部变量又可以重新使用相同的地址。当一块数据被反复读写,其数据会留在cache中,访问速度很快。但静态变量不在堆栈中,所以说是较为低效的。
参考博文:http://t.csdn.cn/cQFh2