合適的資料結構與演算法
編寫出編譯器能夠有效優化以轉換成高效可執行**的原始碼。
將運算量特別大的計算,可以分成多部分,這些部分可以在多核多處理器的某種組合上並行處理
本篇主要以第二點進行討論,編譯器在優化的時候只會做最壞打算,做各種假設。為了保證程式的準確性,捨棄效能優化。
void
twiddle1
(long
*xp,
long
*yp)
void
twiddle2
(long
*xp,
long
*yp)
void
twiddle1
(long
*xp,
long
*yp)
void
twiddle2
(long
*xp,
long
*yp)
**則可以寫成上面這樣,這時候兩個函式的意義就不同了,twiddle1將xp增加了4倍,twiddle2將xp增加了3倍。
兩個指標指向同乙個記憶體位址,稱之為:記憶體別名使用,編譯器必須假設不同指標可能指向相同位址,限制了優化策略
longf(
);long
func1()
long
func2()
看起來兩個函式結果是相同的,但假設函式f內部為:
long counter =0;
longf(
)
那麼func1和func2返回結果就不同了,而且修改全域性狀態也不同。
大多數編譯器不會試圖判斷乙個函式是否沒有***,如果沒有,可能被優化成func2的形式。否則假設最糟糕的情況,保持不變。
用內聯函式減少開銷,也對展開**做了優化。下面程式是將元素累加,之後我們對combine1函式進行一步步優化
int
get_vec_element
(vec_ptr v,
long index, data_t *dest)
long
vec_length
(vec_ptr v)
void
combine1
(vec_ptr v, data_t *dest)
}
將冪等函式移動出迴圈
void
combine2
(vec_ptr v, data_t *dest)
}
get_vec_element中對邊界檢查,雖然很有用,但在本例中能明顯看出所有引用都是合法,可以去除
void
combine3
(vec_ptr v, data_t *dest)
}
combine3將計算值累積在指標dest位置,每次迴圈都會進行三步操作
讀取dest值
讀取data[i]值
寫入dest所在記憶體
從dest讀取的值,就是上次寫入dest的值,加上個臨時的變數
讀data[i]值,計算結果放到臨時記憶體
記憶體的值不寫入,當迴圈完畢最後在寫入到dest
優化後每次迴圈只需要讀取一次data[i]值即可。
void
combine4
(vec_ptr v, data_t *dest)
*dest = acc;
}
編譯器應該也會幫我們優化成combine4的形式,但由於記憶體別名的使用,可能會有不同行為。
比如 *dest引用的是data的位址,那結果會有區別,所以不會優化。
增加每次迴圈迭代計算的元素數量,減少迭代次數。
void
combine5
(vec_ptr v, data_t *dest)
*dest = acc;
}
編譯器一般會幫助我們進行迴圈展開操作,只要優化等級3或更高。
在**層面,下面三行是一條條按序執行,但cpu發現a和b的賦值操作是互不影響的,會同時執行a和b,這種現象叫做:指令級並行。
int a =1;
int b =2;
int c = a + b;
所以我們可以將combine5的**再進行一次優化:
void
combine6
(vec_ptr v, data_t *dest)
*dest = acc0 + acc1;
}
combine5的迴圈展開,將迴圈次數減少了一半,提高了效率。但是cpu執行還是按序執行,無法進行並行操作,因為新的acc值總要依靠上乙個acc,必須按順序執行,才能獲取。
現在將奇數索引的值賦給acc0,偶數索引的值賦給acc1,兩個變數互不影響,cpu就可以進行並行操作了,最後將結果相結合
迴圈展開的數量並不是越多效率越高,迴圈的變數一旦超過可用暫存器數量,效率反而會更慢。案例
**引用-婉兒飛飛
下面**,對陣列值大於等於128的進行求和。求和前進行排序要比不進行排序直接求和效率要高3倍左右。
#include
#include
#include
intmain()
}double elapsedtime = static_cast<
double
>
(clock()
- start)
/ clocks_per_sec;
std:
:cout << elapsedtime << std:
:endl;
std:
:cout <<
"sum = "
<< sum << std:
:endl;
}
cpu對分支進行**,猜測下一步走哪個分支。如果猜對,cpu不會暫停一直執行,猜錯,就要停止-回滾-熱啟動。
cpu根據歷史進行猜測下一步走向。有序的陣列,**判斷條件的結果更加準確,無序陣列無法**,命中率低,導致效率低。
優化方案
如果可以的話移除分支
//用位運算 替換 if判斷
int t =
(data[c]
-128
)>>31;
sum +
=~t & data[c]
;
提高分支的可**性,比如上面先排序
stackoverflow-分支**
優化程式效能(CSAPP)
一 程式優化綜述 1 高效程式的特點 1 適當的演算法和資料結構。方法和資料的組織形式無疑是最關鍵的,是優化的基礎 2 能夠被編譯器轉化成高效的可執行 需要深入了解使用的編譯器的優化方法,和常見的優化策略 3 運用現代並行程式設計技術。多核以及硬體支援提供更大的加速可能,例如gpu 2 優化程式的一...
CSAPP 優化程式效能
1.選擇合適的演算法和資料結構。2.編寫出編譯器能夠有效優化以轉換為高效可執行的源 3.平行計算。當然重點還是第乙個,良好的演算法和資料結構大大減小了程式的時間複雜度。編譯器可以對程式進行不同程式的優化,在終端中,編譯時新增命令列選項 o1,o2等等可以進行不同級別的優化,這樣雖然提高了程式的效能,...
CSAPP 優化程式效能 二
程式示例 為了說明乙個抽象程式是如何被系統地轉換成更有效的 的,我們使用基於如下所示的向量資料結構的執行示例 向量由兩個記憶體塊表示,頭部和資料陣列,頭部宣告結構如下,data t代表基本資料型別 typedef struct vec rec,vec ptr 生成向量,訪問向量元素,確定向量長度的基...