優化程式效能

2021-05-22 23:40:28 字數 3103 閱讀 3120

編寫高效程式需要兩個活動:第一,我們必須選擇一組最好的演算法和資料結構;第二,我們必須編寫出編譯器能夠有效優化以轉換成高效可執行**的源 **。這裡,我們主要講述後者。

首先,我們討論一下為什麼要編寫高效程式。不難想象,如果本來要用10天執行完的程式,經過優化只需要1天就可執行完,這是一件多麼令人振奮的 事啊。時間就是金錢吶。那麼,什麼時候才有必要優化。什麼?優化不是無論什麼時候都有必要的嗎?太不可思議了!當然,作為乙個程式設計師,我們必做在實現與維 護程式的簡單性與它的執行速度之間做出權衡折衷。對於乙個只會執行一次以產生一組資料點的程式,以一種儘量減少程式設計工作量並保證正確性來編寫程式就更為重 要了。考慮一下,比如乙個只用一次的演算法,編寫時間加上執行時間不超過一天,然而我們花上三天來優化這個演算法讓它只要乙個小時就能出結果。乍一看多好的優 化啊,三天變成一小時!等等,讓我們來算一算。不優化編寫加執行時候只要一天,而優化後呢?三天加一小時!當然,如果這個演算法反覆執行的話,我們對它的優 化就值得肯定了。

好了,說了這麼多,切入正題,怎樣才能在優碼級別上進行優化呢?做那些編譯器不能幫你做的優化。這裡,我們先講個例子。考慮乙個簡單向量資料結構。向量由兩個儲存器塊表示。頭部是乙個宣告如下的結構:

typedef structvec_rec, *vec_ptr;

這個宣告用資料型別data_t作為基本元素的資料型別。可以用int,float,double型別來評價我們**的效能,這裡我們使用float。**如下:

typedef float data_t;

除了頭以外,我們還分配乙個len長度的data_t型別物件的陣列,以存放實際的向量元素。**如下:

vec_ptr new_vec(int len)

result->data = data;

}else

result->data = null;

return result;

}當然,還有另外的操作,如取data_t型別物件陣列中的元素,得陣列的長度等。

int get_vec_elment(vec_ptr v, int index, data_t* dest)

int vec_length(vec_ptr v)

作為乙個優化示例,必須有操作。這裡我們將操作定義為把data_t型別物件陣列中的元素根據某種運算合併成乙個值。通過使用編譯時常數ident和oper定義:

#define inent 1

#define oper *

最後,我們進行操作,函式如下:

void combine1(vec_ptr v, data_t* dest)

}這裡就是將所有的元素通過乘法合併成乙個元素。假設元素陣列有10億個。預設地,編譯器產生的**沒有經過任何的優化,所以,我們這個程式的執行時間是相當的長。還是先說明下我的實驗環境吧。我是在eclipse+mingw下執行的,cpu為t5670 1.8ghz。那麼這樣的**花了我多長時間呢?答案是12.680秒!天吶,那不是很慢嘛!這個嘛,要得益於我們高速發展的硬體裝置了。但是,現在只是10億個,要是更多呢?操作只是簡單的相乘,要是更複雜呢?不敢想象。。。

至此,我們先來討論第乙個優化:消除迴圈的低效率 。觀察combine1函式,我們發現,在for(i = 0; i < vec_length(v); i++)中,我們呼叫函式vec_length()作為測試條件。想象一下上c語言課程時候對迴圈的討論,每次迴圈迭代時都必須對測試條件進行求值。哇,那我們執行10億次乘法不是要呼叫10億次vec_length()函式,但是,vec_length()的返回值在這10億次中根本不會變化!沒錯,我們對乙個不會變的結果執行計算了10億次!事實上10億減1次是根本不需要的!你想到了什麼?沒錯,我們可以優化。正如在前面所說,我們要消除迴圈的低效率。

我們編寫combie2版本,它在開始時呼叫vec_length()函式,並將結果賦值給區域性變數length,然後在for迴圈中使用這個變數。果不其然,我們提高了程式的效能,執行完只花了10.012秒。這裡列出combine2的**:

void combine2(vec_ptr v, data_t* dest)

}這個優化是一類常見的、稱為**移動的優化例項。這類優化包括識別出在迴圈裡執行多次,但結果不會變化的計算,因而我們可以將計算移動到迴圈體外,這樣這個計算就不會被執行多次。

下面,我們對第二個優化進行討論:減少過程呼叫 。過程呼叫可能會帶來相當大的開銷。如combine2中的get_vec_elment()函式。每次迭代迴圈,我們都要呼叫get_vec_elment()函式以獲得下乙個元素。仔細觀察**,我們發現完全可以避免這個過程呼叫,因而也不需要進行邊界檢查,對程式來說是乙個良好的優化。我們可以進行如下 的改變:

data_t* get_vec_start(vec_ptr v)

void combine3(vec_ptr v, data_t* dest)

}相比之前,我們在進行迴圈體之前先取得元素陣列的起始位置,然後我們每次迴圈時用陣列得到元素,而省去了對get+vec_elment的過程呼叫,減少了一些執行時間。改善後的時間是6.36秒。

下面,我們再次進入下乙個優化階段:消除不必要的儲存器引用 。我們知道,作業系統中對資料的讀取與儲存,暫存器快於儲存器。然而,我們發現combine3中,每次迴圈中, *dest = *dest oper data[i]語句先是對*dest進行讀取,然後進行計算,再存到*dest中,這些是在儲存器上進行的。但是,我們這一次存的資料就是我們下一次迴圈讀的資料,這樣在儲存器上操作不是很費時間?沒錯,所以,我們要消除不必要的儲存器引用,將這個資料存到暫存器中。我們引入乙個臨時變數:

void combine4(vec_ptr v, data_t* dest)

*dest = tmp;

}經過如此改變之後,我們程式的效能又有所提高,只需要6.227秒。

最後,我們再一次來回顧一下我們如何提高程式的效能。一、消除迴圈的低效率。二、減少過程呼叫。三、消除不必要的儲存器引用。這裡,有人會問,編譯器不是自己有優化的嘛。沒錯,現代的各種編譯器都有能力不等的優化。但是,作為乙個合格的程式設計師,把程式優化的工作交給編譯器固然有益處,可編譯器也不是萬能的啊。一部分的優化能是做不到的,而且,作為優化,最重要的是不能改變程式原來的執行結果。編譯器當碰到能決定是否會改變你的程式結果的時候,他往往選擇不優化以保證結果的正確性,這個時候就需要我們手動來進行程式的優化了。所以,掌握這個技能還是很有必要的。

另外,這是我第一次寫blog,當然文筆很生疏啦,請讀者見諒!不管怎麼樣,這是我學習過程的乙個總結,請批評指正。

優化程式效能

l 消除迴圈的低效率 n 對於迴圈中的過程呼叫盡量移出迴圈外,例如 nfor i 0 i strlen s i strlen 函式為線性增長 在字串長度很大時 很消耗系統資源 n 減少不必要的儲存器引用,將儲存器引用儲存在臨時變數中.l 處理器優化 即充分利用儲存器流水線操作的吞吐量 n 迴圈展開,...

優化程式效能

研究彙編 是理解編譯器以及產生的 會如何執行的最有效的手段之一。編譯器優化 的限制 1 程式設計中存在 儲存器別名使用 的問題。編譯器必須假設不同的指標可能指向儲存器中相同的位置。2 函式呼叫 簡略了。具體看書 基本的編碼原則 效能大幅度提公升 優化程式效能的一些方法 1 將除錯完的程式完成編譯器級...

cpp程式效能優化

1.重中之重 演算法優化 程式效能優化最顯著的優化方法是演算法上的優化,演算法的優化對效能的提公升往往是乙個數量級的,例如排序,冒泡的時間複雜度為o n 2 而快速排序的時間複雜度為o nlog n 這種效能的提供是非常明顯的。2.消除冗餘的迴圈 我們先看一下for迴圈生成的彙編 for int n...