c++程序的优化的原理和其它应用程序没有本质的不同,在优化程序的时候,一定要注意优化的位置,举一个例子:如果优化十处,可以提高0.1秒,但是只要优化另外一处就可以提高十秒,那么重点应该关注哪里,当然是十秒的地方,也就是标题提到的热点。
一、循环热点
有很多的工具和方法用来查找热点位置,热点包括CPU占用,内存占用,IO占用等等。下面举一个CPU热点的例子:
void Test(const char *s)
{
//int len = strlen(s);
for (int num =0;num < strlen(s);num++)
{
...
}
}
这里面就有一个问题,每次for循环,都要重新计算一下strlen(s),如果能够把这个数字提前计算出来,就可以节省很多时间。其它类似的有好的这种情况都可以这样处理。另外一个就是在循环语句中有大量的常量代码:
#define N 100000
void Test()
{
int d = 0;
for (int num = 0;num < N;num++)
{
int i =10;
int j =100;
d = i * j +num;
}
}
这里完全可以把i,j的定义挪到循环外面。另外还有一种在多线程的情况下经常出现的:
//此处在线程内调用这个Test
void Test()
{
for (int num = 0;num < N;num++)
{
....
//wait();
//Sleep(0);
}
}
新手很容易写成在一个线程里调用一个循环然后把工作完成,这样本身没有什么过错,但是如果是在一个大型程序中,这样写的话,用CPU监控器可以发现,一个CPU的核心完全被占满了。其实,工作未必会这样复杂。好的办法,是要Sleep(0)一下,目的不是为了睡眠,目的是让出CPU,或者使用等待机制,都可以解决这个问题。
二、函数热点
函数使用中的热点这里介绍两类:
1、多层调用堆栈
在c语言中,函数是基本的构件,为了完成功能,一定会写N多的函数,函数多无所谓,主要是不要不断的嵌套调用函数(包括谨慎的使用递归调用),看下面的代码:
void Test(const int d)
{
int a[30]={0};
......
}
void Get(const int v)
{
double b[10]={0.0};
Test(v);
}
void GetOnce(const int c)
{
Get(c);
}
函数调用时,会发生上下文的保存,恢复时又要进行逆向的操作,当你的函数中存在大量的变量或者需要保存的数据时,这个代价是不容小觑的,所以一定要谨慎的处理函数的调用包括递归。
2、不必要的虚函数
虚函数的调用其实还是比较好理解的,迟后联编,动态加载,这肯定是需要牺牲性能的。如果在设计一个模块时,可以不使用虚拟函数来完成目标,那么尽量还是放弃它(这里仅用优化的角度谈这个问题,实际情况需要从整体权衡考虑)。
除了上面的两点,其实还可以使用静态多态来提高效率,比如使用模板,把动态加载提前到编译期。当然,这也提高了编程的难度和复杂度。还有减少动态库的调用等。三、内存热点
如果需要不断的分配和销毁内存,这就会出现一个问题,在持续一段时间后,可能查看内存还有不少,但是内存的分配却无法成功了,反复的不断分配不同大小 的内存,生成了大量的内存的碎片,导致后期内存的分配不堪使用:
void Test(int num)
{
int * buf = new int[num];
.....
delete []buf;
}
int random()
{
//返回一个随机值,省略了计算
return n;
}
void CallTest()
{
int n = random();
Test(n);
}
类似上面的代码的情况很多,比如在读写Socket数据时就会有这种情况,那么如何解决呢?一般使用内存池的方式,把内存管理起来,控制分配,一次释放。但是陈硕在网上说“现在一些人动不动就要挽起袖子自己写内存池,号称能提高性能,真当 Ulrich Drepper 是水货?”,参看这篇文章:
https://akkadia.org/drepper/cpumemory.pdf
所以还是要深入钻研,根据实地情况来搞,技术的进步,会导致原来的一些方法失效甚至根本起了反作用,要紧跟技术前进的步伐。
四、IO热点
这个比较好理解,毕竟硬件IO的速度比之CPU和内存要慢好几个数量级,产生热点的可能性会倍增。这里好的方法只有一个,使用好的库,更优秀的语言,并其中进行权衡。当然,在读写IO时,尽量不要使用画蛇添足的方式来操作,比如在不必要的情况下非要显示的每次都Flush。
五、总结
热点其实是解决优化的重点,只要找到了热点,基本上可以说,优化的问题解决了大半,这也是一种抓大放小,哲学上的抓住事物的主要一面。