性能優(yōu)化小技巧-消除低效循環(huán),讓你的程序快到飛起
在分享這些性能優(yōu)化技巧之前,需要說明以下幾點:
- 不要過早優(yōu)化性能
- 現(xiàn)代編譯器的優(yōu)化能力很強大
- 80%的性能問題集中于20%的代碼中
但是由于編譯器的優(yōu)化非常小心,它必須確保優(yōu)化前后執(zhí)行的效果是保持一致的,因此有些時候它會變得保守,并不能幫你優(yōu)化太多。
本文所需要的是在平常不需要花費太多力氣,養(yǎng)成習(xí)慣,并且對程序性能有好處的小技巧。
示例程序
為了說明本文所提到的技巧效果,先看一個示例程序,程序的目的非常簡單,就是將字符串中的小寫字母轉(zhuǎn)換為大寫),以下是完整可編譯運行代碼:
- #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)//生成隨機(jī)數(shù)
- {
- str[i] = ( rand() % ( max - min ) ) + min;
- i++;
- }
- str[MAX_LEN - 1] = 0;
- //統(tǒng)計時間
- 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;
- }
隨機(jī)數(shù)的生成可參考《隨機(jī)數(shù)生成的方法》。我們主要關(guān)注下面的部分:
- for(i = 0;i < strlen(str) ;i++)
- {
- str[i] = toupper( str[i] );
- }
很簡單,對不對?
運行看看時間:
- $ gcc - -o loop loop.c
- $ ./loop
- cost time: 42103 ms
總共花了42秒多!(機(jī)器處理能力不同運行結(jié)果將會有較大差異)
消除低效循環(huán)
終于來到了我們的優(yōu)化環(huán)節(jié),我們觀察代碼其實很容易發(fā)現(xiàn),每次循環(huán)的時候都會執(zhí)行一次strlen計算字符串的長度,而這個計算具有以下特點
每次結(jié)果一致,屬于重復(fù)計算
strlen時間復(fù)雜度為O(N),也就是說,字符串越長,它需要的時間也就越多
一般情況下的使用是沒有太大問題的,但是問題在于,如果是在一個多次循環(huán)中,它能極大的影響效率。
到這里,優(yōu)化方法想必你也清楚了,那就是將計算結(jié)果不會改變的計算移到循環(huán)外。代碼如下:
- unsigned int len = strlen(str);
- for(i = 0;i < len ;i++)
- {
- str[i] = toupper( str[i] );
- }
那么再次運行的結(jié)果如何呢?
- $ gcc -O0 -o loop loop.c
- $ ./loop
- cost time: 4 ms
看到?jīng)]有,4ms,將近一萬的性能提升!而這個數(shù)值將會隨著字符串長度的增長進(jìn)一步擴(kuò)大。驚不驚喜,意不意外?
總結(jié)
實際上,本文的例子是比較極端的,然后實際中就可能隱藏著很多類似的代碼:
- 在循環(huán)中計算,但是每次結(jié)果都一樣
- 并且該計算的復(fù)雜度不是O(1)
對于這類代碼,在不絕對影響可讀性的情況下,完全可以將其移到循環(huán)外。
思考
如果是C++的string,循環(huán)時通過str.length()獲取長度,會如此影響性能嗎?為什么?