最近在看《c++ 性能优化指南》书籍,从书中学习到了不少c++ 程序优化的点,平时代码中一定要注意这些坑,在此记录下来。本篇幅主要是讲下字符串处理性能的优化。
一.下面来看一个简单的例子,我们平时 写代码 不注意的时候,最有可能写出的是如下所示的第一种 remove_ctrl() 形式的代码。
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <iostream>
#include <sys/time.h>
using namespace std;
string remove_ctrl(string s) //没有优化的代码
{
string result;
for(int i =0;i < s.length(); ++ i)
{
if(s[i] >= 0x20)
{
result = result + s[i];
}
}
return result;
}
string remove_ctrl2(string s) //使用复合赋值 避免临时字符串
{
string result;
for(int i =0;i < s.length(); ++ i)
{
if(s[i] >= 0x20)
{
result += s[i];
}
}
return result;
}
string remove_ctrl3(string s) //预留存储空间,减少内存的重新分配
{
string result;
result.reserve(s.length());
for(int i =0;i < s.length(); ++ i)
{
if(s[i] >= 0x20)
{
result += s[i];
}
}
return result;
}
string remove_ctrl4(const string& s) //
{
string result;
result.reserve(s.length());
for(int i =0;i < s.length(); ++ i)
{
if(s[i] >= 0x20)
{
result += s[i];
}
}
return result;
}
void test()
{
uint64_t time1 = 0;
uint64_t time2 = 0;
uint64_t time3 = 0;
uint64_t time4 = 0;
string str = "klhnjuijhsbdcfg@ldkmqaaaohuhjiush";
struct timeval start, end;
gettimeofday(&start,NULL);
for(int i=0;i < 10000;++i)
{
remove_ctrl(str);
}
gettimeofday(&end,NULL);
time1 = ( end.tv_sec - start.tv_sec ) * 1000 * 1000 + end.tv_usec - start.tv_usec;
gettimeofday(&start,NULL);
for(int i=0;i < 10000;++i)
{
remove_ctrl2(str);
}
gettimeofday(&end,NULL);
time2 = ( end.tv_sec - start.tv_sec ) * 1000 * 1000 + end.tv_usec - start.tv_usec;
gettimeofday(&start,NULL);
for(int i=0;i < 10000;++i)
{
remove_ctrl3(str);
}
gettimeofday(&end,NULL);
time3 = ( end.tv_sec - start.tv_sec ) * 1000 * 1000 + end.tv_usec - start.tv_usec;
gettimeofday(&start,NULL);
for(int i=0;i < 10000;++i)
{
remove_ctrl4(str);
}
gettimeofday(&end,NULL);
time4 = ( end.tv_sec - start.tv_sec ) * 1000 * 1000 + end.tv_usec - start.tv_usec;
cout << " time1 is :" << time1 << " us" << endl;
cout << " time2 is :" << time2 << " us" << endl;
cout << " time3 is :" << time3 << " us" << endl;
cout << " time4 is :" << time4 << " us" << endl;
}
int main()
{
test();
return 0;
}
程序运行的结果如下:
从上面的结果可以看到,第一种方式字符串处理 效率最低。
(1).remove_ctrl函数中result + s[i] 运算开销很大,它会调用内存管理器去构建一个临时字符串对象来保存连接后的字符串,若字符串str的长度是n,则需要调用n次内存管理器创建临时字符串对象,需要调用n 次内存管理器来释放内存。
(2).remove_ctrl2 函数优化的主要是 利用复合赋值 避免临时字符串的创建,从上图中可以看出,相比较第一种方法性能提升很多。
(3).remove_ctrl3 中,result 字符串分配的内存空间 是一个动态增长过程,result 字符串会被反复的复制到一个更大的内部缓冲区。 每次result字符串缓冲区发生溢出时,按照std::string 字符串处理方式 会申请两倍的内存空间。那么在整个处理过程中,会根据输入字符串长度大小,多次申请 分配新的内存空间。通过reserve() 函数 预先分配好足够的内存大小空间 来进行优化。
(4).remove_ctrl4 中主要是通过常量引用 传入参数,省去了参数在函数中的一次 昂贵的内存分配。上面3种函数的参数 将会通过复制构造函数来进行初始化,需要一次内存的分配。