讀CSAPP 2 程式效能優化

2021-10-01 13:08:18 字數 3427 閱讀 5397

合適的資料結構與演算法

編寫出編譯器能夠有效優化以轉換成高效可執行**的原始碼。

將運算量特別大的計算,可以分成多部分,這些部分可以在多核多處理器的某種組合上並行處理

本篇主要以第二點進行討論,編譯器在優化的時候只會做最壞打算,做各種假設。為了保證程式的準確性,捨棄效能優化。

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 生成向量,訪問向量元素,確定向量長度的基...