《深刻理解計算機系統》之代碼優化

讀書筆記第6周
《深刻理解計算機體系結構》

主要看了第5章,優化程序性能。如同老師所說的,程序優化的技巧是須要平時不斷積累,並且經過閱讀這一章,充分體會到,真正想要作到程序的優化,下到計算機體系結構,彙編語言,同時對編譯器要有充分的瞭解,上到合適的數據結構以及算法的選擇都會對程序的運行速度產生巨大的影響,除外還有任務的的切割,進行並行運算。可是這一章它主要仍是講的是咱們應當如何正確的編寫代碼來引導編譯器進行優化,仍是頗有意思的。最優秀的是做者使用的測試cpu是intel core i7 Haswell,讓我感受到這些技巧確確實實如今仍舊有意義。算法

分爲兩個部分,看的內容與收穫總結吧

1.瞭解到編譯器的侷限性

所謂的侷限性是編譯器可能不會像你想象中的作一些理想固然的優化,由於編譯器必須保證絕對的安全性,舉了一個很簡單的例子安全

void twiddle1(long *xp ,long * yp){
    *xp+=*yp;
    *xp+=*yp;
}
void twiddle2(long *xp ,long * yp){
    *xp=2*yp;;
}

  

並且咱們按的認爲1,2等效,並且2更高效一點,由於內存讀寫數目少,可是實際上,二者是不等價的,好比xp與yp指向相同元素地址的時候,1變爲4倍,2只有三倍。其後還舉了函數調用的例子,都是強調,編譯器是會自動優化,可是可能並不會爲咱們優化掉一些咱們理算固然的部分,由於可能會帶來安全的隱患。數據結構

2.具體實例

  1. 首先講了代碼移動,這個以前老師也提到過,就是把在循環過程當中的函數返回是不變量的賦值在循環外,避免重複調用,並指出,通常編譯器不會自動作這樣的優化嗎,由於會帶來安全風險,這樣的優化通常是須要咱們本身動手完成的。
  2. 消除沒必要要的內存引用,部分片斷以下:
for(int i = 0;i < length; i++){
    *dest++; //或者其餘運算
}
int q =*dest;
for(int i = 0;i < length; i++){
    q++; //或者其餘運算
}
*dest = q;

  

書中爲咱們展現了二者的彙編代碼,2明顯比1少了取dest值到寄存器再從起存起賦值回去的兩個語句,速度獲得了明顯提高,整型CPE降到了原來的1/7,浮點數1/3.而且舉了例子,編譯器出於安全考慮不會作這樣的優化。(可是書中還介紹了GCC優化選項如何在保證安全性的基礎上作優化,發現本質上仍是引入了中間值,不過每一次都會更新*dest的值,至關於減小一次取值到寄存器的操做)併發

3.理解現代處理器

介紹了一些現代處理器的一些技術,如指令級並行(即如何在底層實現並行運算,可是上層上卻顯示出順序執行的結果),分支預測(在條件轉移指令處預測轉移地址)等等。
比較新奇一點是看到了退役單元的概念,其是一個寄存器文件,儲存着最近的指令操做,寄存器更新,只有當一個指令以前全部相關的分支點都確認預測成功了,這條指令就退役,計算結果才真正寫入程序下相關的寄存器,也就是說以前用的寄存器都是閒暇的(我猜的,書中寫的不是特別清楚),反之,以前有分支預測錯誤,此指令全部涉及的計算結果所有丟棄。這樣就保證了預測錯誤時結果的正確性。 這樣,必然就出現了一個指令之間寄存器值傳遞的問題,相應的誕生了寄存器重命名機制,可是我沒看懂具體是怎麼實現的,可是理解了它就是經過一張表來實現寄存器間傳遞,而不是一條指令寫入寄存器,另外一條去讀,這樣就保證了只有退役指令才能寫寄存器。函數

4.增長累加變量進行循環展開

這是我看到的最精彩的一節,簡直徹底刷新了個人三觀

循環展開是並行運算的一個應用,爲了講這個,先是引入了循環的關鍵路徑的概念(其實直接理解爲CPE是能夠的),就是說,必定程度上一個循環體內的計算語句不是順序的,而是並行的(循環標誌的++與數據的運算,之前歷來沒有這個概念),通常是數據的浮點運算成爲瓶頸,其運算也構成一次循環完成的關鍵路徑,完成循環的時間=關鍵路徑*循環次數,而後做者舉了一個例子:性能

for(int i = 0 ; i < length ; i++){
    acc=acc op date[i];
}
for(int i= 0; i < length ; i=i+2){
    acc1 = accc1 op date[1];
    acc2 = acc2  op date[i+1];
 }

  

會發現2會比1快接近一倍,爲何呢,是由於增長一個累計變量後,其實acc1與acc2的計算基本是並行的(多加了一個功能單元),2中每次循環完成時間與1基本相同,可是循環次數減半了!!!! 固然這種優化也是有上限的,最多展開到相應的功能單元個,也就仍是說,只要相應功能的計算單元流水線滿了,就沒有展開的餘地了。(這就要求咱們要對本身的cpu計算單元有所瞭解,好比處理整數加法的有多少個,浮點數乘法的有多少個)測試


心得總結

1. 之前一直以爲編譯器很強大,寫的代碼爛一點也能幫我優化,如今知道不是了,仍是須要花心思,自行優化。
2. 多用並行的觀點寫代碼,爭取之後作到優化的時候心中能將併發操做並排,使勁往懟功能單元,讓整個cpu都忙起來。
相關文章
相關標籤/搜索