代碼優化技巧2:循環優化
本文算不上什麼很有獨創性的東西,在很多人的文章和書籍中都有看到,比如深入理解操作系統在的5/4,韓天峯老師的謀篇<php需要聰明人的語言>也提到過一些相關內容.
在大多數的可以允許過程化編程的語言中,循環永遠是經常用到的結構.但是每一次使用循環我們都要尤其小心謹慎,避免循環出現的低效率.我們在這裏使用一個有趣的例子.
避免在循環條件中使用函數
考慮下面一個循環
string v;
long i;
for (i = 0; i <= getlength(v); i++) {
data_v val;
get_vec_element(v, i);
val->i += v;
}
我們在循環體裏面訪問函數獲取v的長度.很容易我們就看出這是低效率的.我們可以吧獲取長度放到外面來.
string v;
long i;
long length = getlength(v);
for (i = 0; i <= length; i++) {
data_v val;
get_vec_element(v, i);
val->i += v;
}
很容易的有些人會以爲編譯器會足夠的只能回自動優化成第二類代碼.實際上並非如此.因爲編譯器是無法知道函數是否會變v產生額外影響.即便是採用內聯函數優化,編譯器也沒如此強大的分析能力.
在極端的情況下,比如吧字符串全部大寫的函數,每一次循環都訪問strlen,這樣函數的時間複雜度會變從線性轉化爲二次.複雜度鬥爭.
循環展開
循環展開是一種優化方法,就是增加每一次迭代計算的數目,減少迭代的次數從而達到優化代碼的目的.
void combine5(double data[],int length)
{
double sum = 0.0;
for(int i=0;i<length;i++)
{
sum *= data[i];
}
cout<<sum<<endl;
}
void combine6(double data[],int length)
{
double sum = 0.0;
int limit = length-1;
int i;
for(i=0;i<limit;i+=2)
{
sum = sum*data[i]*data[i+1];
}
for(;i<length;i++)
{
sum *= data[i];
}
cout<<sum<<endl;
}
void combine7(double data[],int length)
{
double sum1=0.0,sum2=0.0;
int limit = length-1;
int i;
for(i=0;i<limit;i+=2)
{
sum1 *= data[i]; // 合併下標爲偶數的值, 0按偶數算
sum2 *= data[i+1]; // 合併下標爲奇數的值
}
double sum = sum1*sum2;
for(;i<length;i++)
{
sum *= data[i];
}
cout<<sum<<endl;
}
combine5只是做了一些簡單的優化,combine6進行了循環展開,combine7既循環展開又多路並行.由於cpu流水線的存在.cpu可以大幅度優化代碼的效率.
有趣的是編譯器是很容易執行循環展開的,只要把gcc的優化等級調高到o3就可以直接使用循環展開優化.
迭代器模式
我再次強烈推薦使用迭代器模式.比如php中最常見的foreach就是迭代器模式的體現.使用迭代器模式有幾個好處
- 避免手動寫循環條件造成低效率
- 減少內存佔用.由於迭代器模式下不會訪問整個數組/容器.從而減少內存佔用.
- 更少的出錯.毋庸置疑使用迭代器可以避免手動寫循環導致的錯誤