C++性能优化之一:合理使用内存

要想在编码过程中,写出高效的代码,是需要自己长期的总结和不断学习的。工作以来,我自己也总结了一些小技巧,可以让你的程序运行的更快、内存空间使用更合理,同时我还会不断地补充该blog,争取建立出一个属于自己的c++ effective系列。

不多说,直接进入正题,以下都是我再编程过程中,总结出来c++高效编码规则,每个topic对应一个规则。

局部变量合理使用

让我们先看一段代码:

1
2
3
4
5
for (int i = 0; i < 1000; ++i)
{
string str = "do some thing:" + int2str(i);
func(str);
}

这段代码,在循环中使用局部变量拼装函数func的入参,在每次循环过程中,str对象都会执行一次构造函数和析构函数,那么,在这个for循环中,单单是str的组装就耗费了1000次的内存申请和释放,局部变量占用内存小的话,影响不会很大,如果动辄几十、几百kb,那就会造成系统内存使用的波动,那么是不是有更高效的方法?

其实只需要把str变量放到for循环外部声明即可,如下面代码:

1
2
3
4
5
6
string str;
for (int i = 0; i < 1000; ++i)
{
str = "do some thing:" + int2str(i);
func(str);
}

这段代码会大大降低内存的申请和释放次数,因为首次循环后,str会申请15个字节的内存空间来容纳现有数据,第二次循环时,在赋值运算符函数中,由于str当前空间已经足够容纳第二次循环的数据,因此我们可以考虑对原有str内存进行复用,所以只存在一次数据拷贝,不存在新的内存申请和释放;到第十次循环时,需要16个字节才能容纳现有数据,因此需要释放str原有内存,申请新的内存。以此类推,我们可以算出1000次循环过程中,只有三次内存申请和释放,大大降低了内存的申请和释放次数。

小结:在循环体中,局部变量如果占用内存空间较大,会造成内存使用不合理,可以考虑放到循环体外声明

左值引用的合理使用

左值引用提升程序性能的应用场景。

首先是函数入参,看下面两个函数的声明,func1会存在一次str副本的拷贝构造的过程,且退出函数体,还需要释放str,而func2直接将str的地址传入函数体内部,不存在拷贝构造,如果str内存很大,那么节约一次拷贝的收益还是很可观的。

1
2
void func1(const string str); //存在冗余拷贝构造和析构
void func2(const string& str); //直接传递str变量的地址

其次是循环体中,获取数组元素时,如果我们不需要修改原始值,那么应该是使用常引用直接指向数组元素的地址,避免局部变量的冗余的拷贝构造和析构

1
2
3
4
5
6
for (int i = 0; i < arrstrs.size(); ++i)
{
string str = arrstrs[i]; //存在冗余拷贝构造和析构
const string& str = arrstrs[i]; //直接使用arrstrs[i]变量的地址
...
}

动态数组容量提前设定

分层架构的代码中,经常出现需要对不同层次数据规格进行转换,即把其他层次的数据转化为所在层的数据格式,以下是项目中经常看见的一段代码,主要目的是把第二层的数据转化到第一层坐标数据中,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//变量格式声明
typedef struct _FirstLayer_PosData_t
{
double x;
double y;
}FirstLayer_PosData_t;
typedef struct _SecondLayer_PosData_t
{
double x;
double y;
int tag;
}SecondLayer_PosData_t;
vector<FirstLayer_PosData_t> arrfir;
vector<SecondLayer_PosData_t> arrsec;
//层数据转化代码
for (int i = 0; i < arrsec.size(); ++i)
{
FirstLayer_PosData_t stFirstLayerPos;
stFirstLayerPos.x = arrsec[i].x;
stFirstLayerPos.y = arrsec[i].y;
arrfir.push_back(stFirstLayerPos);
}

这段代码可以这样改进,其实我们要拷贝的元素个数是已知的,因此我们可以直接将arrfirst数组大小设置为arrsecond的大小即可,这就避免了在循环体中动态的去扩容(每次扩容的成本是先申请新的内存空间,将旧内存空间数据拷贝到新内存空间,然后释放旧内存空间),改进代码如下:

1
2
3
4
5
6
7
8
arrfir.setsize(arrsec.size());
for (int i = 0; i < arrfir.size(); ++i)
{
FirstLayer_PosData_t stFirstLayerPos;
stFirstLayerPos.x = arrsec[i].x;
stFirstLayerPos.y = arrsec[i].y;
arrfir[i] = stFirstLayerPos;
}

仔细观察下,其实还有优化空间,局部变量是可以避免的,直接使用引用代替第一层数组的每个元素即可,最终优化代码如下:

1
2
3
4
5
6
7
arrfir.setsize(arrsecond.size());
for (int i = 0; i < arrfirst.size(); ++i)
{
FirstLayer_PosData_t& stFirstLayerPos = arrfir[i];
stFirstLayerPos.x = arrsec[i].x;
stFirstLayerPos.y = arrsec[i].y;
}

move语义的合理使用