研究彙編代碼是理解編譯器以及產生的代碼會如何啟動並執行最有效手段之一。
編譯器最佳化代碼的限制:
1、程式設計中存在“儲存空間別名使用”的問題。編譯器必須假設不同的指標可能指向儲存空間中相同的位置。
2、函數調用(簡略了。。。具體看書)
~~~~~~~~~~~~~~~基本的編碼原則:效能大幅度提升~~~~~~~~~~~~~~~~~~~
最佳化程式效能的一些方法:
1、將調試完的程式完成編譯器層級上的最佳化。(這個目前還沒找到。。。)
2、代碼移出
這類最佳化包括識別出要執行多次(例如,在迴圈裡)但是計算結果不會改變的計算,因而我們可以把計算移動到代碼前面的、不會被多次求值的部分。
3、減少程序呼叫
因為程式執行過程中程序呼叫(反覆的程序呼叫)是非常耗時的,可以考慮是否能夠損失部分的模組性和抽象性來提高程式的效率。
當然這樣做肯定會損害程式的可讀性,但是只要稍加註釋,完全可以避免的。
完全可以對資料進行直接的訪問。
4、消除不必要的儲存空間引用
通過使用局部變數來代替實際的頻繁的儲存空間引用。這樣做對程式執行速度的提高是非常明顯的。
在所有的基本算術運算中 除法運算是最消耗時間的。
~~~~~~~~~~~~~~~~低層級的程式最佳化技術:效能提升很小的~~~~~~~~~~~~~~~
降低迴圈開銷:
可以通過在每次迭代中執行更多的資料操作來減少迴圈開銷的影響,使用的是迴圈展開的技術。其思想是在一次迭代中對多個數組元素進行訪問和合併作業。這樣得到的程式需要更少的迭代,從而降低了迴圈的開銷。
如果迴圈展開k次,就把上限設定為n-k+1.那麼最大的迴圈索引i+k-1會比n小。
void combine5(vec_ptr v,data_t* dest){
int length=vec_length(v);
int limit=length-2;
data_t* data=get_vec_start(v);
data_t x=INDENT;
int i;
//Combine 3 elements at a time
for(i=0; i<limit ; i+=3){
x= x OPER data[i] OPER data[i+1] OPER data[i+2];
}
//Finish any remaining elements
for( ; i<length ;i++){
x= x OPER data[i];
}
*dest =x;
}
轉換到指標代碼:
在運算元組的程式中,可以方便得把數組轉化為指標操作,但是這樣做的結果是:程式執行效率上不會有太大的提高。
提高並行性:
對於一個可結合可交換的操作(前提條件,沒有這個,扯淡)來說,比如說整數的加法和乘法,我們可以通過將一組合併作業分割成兩個或多個部分,並在最後合并結果來提高效能。
void combine6(vec_ptr v,data_t* dest){
int length=vec_length(v);
int limit=length-1;
data_t* data=get_vec_start(v) ;
data_t x0=INDET;
data_t x1=IDENT;
int i;
//Combine 2 elements at a time
for(i= 0; i<limit ; i+=2){
x0= x0 OPER data[i];
x1= x1 OPER data[i+1];
}
//Finish any remaining elements
for( ; i<length ; i++){
x0 =x0 OPER data[i];
}
*dest=x0 OPER x1;
}
這裡使用的是2次展開迴圈並使用二路並行。
但是注意:IA32指令集只有很少量的寄存器來存放累積的值。如果並行度p超過了當前可用的寄存器數量,編譯器會訴諸於溢出(spilling),將某些臨時值存放到棧中,這樣效能會急劇下降。