在分享这些性能优化技巧之前,需要说明以下几点:
- 不要过早优化性能
- 现代编译器的优化能力很强大
- 80%的性能问题集中于20%的代码中
但是由于编译器的优化非常小心,它必须确保优化前后执行的效果是保持一致的,因此有些时候它会变得保守,并不能帮你优化太多。
本文所需要的是在平常不需要花费太多力气,养成习惯,并且对程序性能有好处的小技巧。
示例程序
为了说明本文所提到的技巧效果,先看一个示例程序,程序的目的非常简单,就是将字符串中的小写字母转换为大写),以下是完整可编译运行代码:
#include<stdlib.h>
#include<stdio.h>
#include<time.h>
#include<ctype.h>
#include<string.h>
#include<sys/time.h>
#define MAX_LEN 1024*1024
void printCostTime(struct timeval *start,struct timeval *end)
{
if(NULL == start || NULL == end)
{
return;
}
long cost = (end->tv_sec - start->tv_sec) * 1000 + (end->tv_usec - start->tv_usec)/1000;
printf("cost time: %ld ms\n",cost);
}
int main(void)
{
srand(time(NULL));
int min = 'a';
int max = 'z';
char *str = malloc(MAX_LEN);
//申请失败则退出
if(NULL == str)
{
printf("failed\n");
return -1;
}
unsigned int i = 0;
while(i < MAX_LEN)//生成随机数
{
str[i] = ( rand() % ( max - min ) ) + min;
i++;
}
str[MAX_LEN - 1] = 0;
//统计时间
struct timeval start,end;
gettimeofday(&start,NULL);
for(i = 0;i < strlen(str) ;i++)
{
str[i] = toupper( str[i] );
}
gettimeofday(&end,NULL);
printCostTime(&start,&end);
free(str);
str = NULL;
return 0;
}
- 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.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
随机数的生成可参考《随机数生成的方法》。我们主要关注下面的部分:
for(i = 0;i < strlen(str) ;i++)
{
str[i] = toupper( str[i] );
}
- 1.
- 2.
- 3.
- 4.
很简单,对不对?
运行看看时间:
$ gcc - -o loop loop.c
$ ./loop
cost time: 42103 ms
- 1.
- 2.
- 3.
总共花了42秒多!(机器处理能力不同运行结果将会有较大差异)
消除低效循环
终于来到了我们的优化环节,我们观察代码其实很容易发现,每次循环的时候都会执行一次strlen计算字符串的长度,而这个计算具有以下特点
每次结果一致,属于重复计算
strlen时间复杂度为O(N),也就是说,字符串越长,它需要的时间也就越多
一般情况下的使用是没有太大问题的,但是问题在于,如果是在一个多次循环中,它能极大的影响效率。
到这里,优化方法想必你也清楚了,那就是将计算结果不会改变的计算移到循环外。代码如下:
unsigned int len = strlen(str);
for(i = 0;i < len ;i++)
{
str[i] = toupper( str[i] );
}
- 1.
- 2.
- 3.
- 4.
- 5.
那么再次运行的结果如何呢?
$ gcc -O0 -o loop loop.c
$ ./loop
cost time: 4 ms
- 1.
- 2.
- 3.
看到没有,4ms,将近一万的性能提升!而这个数值将会随着字符串长度的增长进一步扩大。惊不惊喜,意不意外?
总结
实际上,本文的例子是比较极端的,然后实际中就可能隐藏着很多类似的代码:
- 在循环中计算,但是每次结果都一样
- 并且该计算的复杂度不是O(1)
对于这类代码,在不绝对影响可读性的情况下,完全可以将其移到循环外。
思考
如果是C++的string,循环时通过str.length()获取长度,会如此影响性能吗?为什么?