程式示例
為了說明乙個抽象程式是如何被系統地轉換成更有效的**的,我們使用基於如下所示的向量資料結構的執行示例
向量由兩個記憶體塊表示,頭部和資料陣列,頭部宣告結構如下, data_t代表基本資料型別:
typedef struct vec_rec, *vec_ptr;
生成向量,訪問向量元素,確定向量長度的基本過程
vec_ptr new_vec(long len)
}result->data = data;
return result;
}
int get_vec_element(vec_ptr v, long index, data_t *dest)
long vec_length(vec_ptr v)
作為優化示例考慮下面**,將乙個向量中所有元素合併成乙個值
通過不同的巨集定義來執行不同的運算
#define ident 0
#define op +
#define ident 0
#define op *
void combine1(vec_ptr v, data_t *dest)
}
combine1 的cpe度量值
combine1呼叫函式vec_length作為for迴圈的測試條件,每次迴圈迭代都必須對測試條件求值,另一方面,向量的長度並不會隨著迴圈的進行而改變,我們對程式進行修改
改進迴圈測試效率,通過吧vec_length()移出迴圈,我們不需要每次迭代都執行這個函式
void combine2(vec_ptr v, data_t *dest)
}
combine2的cpe度量值
對於這種改變在**呼叫函式,呼叫函式多少次的變換,編譯器會非常的小心謹慎,程式設計師必須幫助編譯器顯示地完成**移動。
減少過程呼叫
過程呼叫會帶來開銷,妨礙大多數形式的程式優化
從combine2的**可以看出,每次迴圈迭代都呼叫get_vec_element來獲取下乙個向量元素,對每個向量都進行邊界檢查,很明顯會造成低效率,在處理任意陣列訪問時,邊界檢查很有用,但是對combine2的**簡單分析表明,所有的引用都是合法的。
為我們的資料型別增加函式get_vec_start來獲取陣列起始位址來獲取元素
data_t *get_vec_start(vec_ptr v)
void combine3(vec_ptr v, data_t *dest)
}
檢視combine3的cpe度量圖
驚奇的是效能沒有提公升,整數求和還略有下降,顯然內迴圈中的其他操作形成了瓶頸,限制效能超過get_vec_element,後文我們還會再回到這個函式,看看為什麼combine2中反覆的邊界檢查不會讓效能更差。
消除不必要的記憶體引用
再次我們給出資料型別為double(8位元組)
檢查編譯內迴圈產生的**
//dest in %rbx, data+i in %rdx, data+length in %rax
.l17:
vmovsd (%rbx), %xmm0
vmulsd (%rdx), %xmm0, %xmm0
vmovsd %xmm0, (%rbx)
addq $8, %rdx
compq %rax, %rdx
jne .l17
每次迭代,累計變數的值都要從記憶體讀出再寫回記憶體,這樣的讀寫很浪費,因為每次迭代開始時從dest讀出的值就是上次迭代最後寫入的值
所以我們隊程式再次進行改進,引入臨時變數acc(累積器accumulator),再迴圈中用來累計計算出的值,只有迴圈迭代結束才存放入dest,將combine3每次迭代的2次讀,1次寫減少到只需要1次讀。
void combine4(vec_ptr v, data_t *dest)
x86-64彙編**
// acc in %xmm0, data+i in %rdx, data+length in %rax
.l25:
vmulsd (%rdx), %xmm0, %xmmo
addq $8, %rdx
cmpq %rax, %rdx
jne .l25
檢視combine4的cpe度量值
可能有人會認為,編譯器會自動將combine3中的**轉換為combine4中的**所做的那樣,但是實際上由於記憶體的別名使用(兩個指標指向同一塊記憶體的情況),兩個函式會有不同的行為。
優化程式效能(CSAPP)
一 程式優化綜述 1 高效程式的特點 1 適當的演算法和資料結構。方法和資料的組織形式無疑是最關鍵的,是優化的基礎 2 能夠被編譯器轉化成高效的可執行 需要深入了解使用的編譯器的優化方法,和常見的優化策略 3 運用現代並行程式設計技術。多核以及硬體支援提供更大的加速可能,例如gpu 2 優化程式的一...
CSAPP 優化程式效能
1.選擇合適的演算法和資料結構。2.編寫出編譯器能夠有效優化以轉換為高效可執行的源 3.平行計算。當然重點還是第乙個,良好的演算法和資料結構大大減小了程式的時間複雜度。編譯器可以對程式進行不同程式的優化,在終端中,編譯時新增命令列選項 o1,o2等等可以進行不同級別的優化,這樣雖然提高了程式的效能,...
讀CSAPP 2 程式效能優化
合適的資料結構與演算法 編寫出編譯器能夠有效優化以轉換成高效可執行 的原始碼。將運算量特別大的計算,可以分成多部分,這些部分可以在多核多處理器的某種組合上並行處理 本篇主要以第二點進行討論,編譯器在優化的時候只會做最壞打算,做各種假設。為了保證程式的準確性,捨棄效能優化。void twiddle1 ...