如何編寫高效的代碼(2014/6/1)

編寫高效的代碼有兩個條件:選擇好的算法和數據結構,編寫編譯器能夠優化以轉換成高效可執行的代碼。前者是基礎和前提,即使後者做的足夠好,但是選用了錯誤的算法和數據結構,優化也不起作用,這個一點要搞清楚。本文的內容的側重於後者。

1 計算機系統架構


L1和L2位於CPU芯片上,L3被各個CPU共享。由於成本的考慮,L3,L2,L1的大小依次遞減。以INTEL XEON E7-8891V2爲例,L3 37.5 MBL2 2.5 MBL1 640 KB

2 各種存儲的速度比

 

 

寄存器

 

L1

10-13

L2

10-12

L3

10-11

內存

10-10

磁盤

10-3

 從上表中可以看出,內存比磁盤快1000萬倍。

3 進程的內存分佈

共享映射庫的起始地址是0x40000000(1G),BSS: 未初始化的全局變量。

4 函數調用的開銷

 

ebp寄存器指向當前幀的棧底,esp寄存器指向當前幀的棧頂。函數被調用時,都會生成一個新的幀。返回地址:函數調用返回調用者後,待執行的代碼的地址。在調用函數之前,調用者在自己的棧內構造參數。緩衝區溢出攻擊的原理:覆蓋被保存的ebp和返回地址,執行攻擊者指定的函數。

5 代碼優化

編寫高效的代碼有兩個步驟:選擇好的算法和數據結構,編寫編譯器能夠優化以轉換成高效可執行的代碼。第一個步驟是前提。即使第二部做的足夠好,但是選用了錯誤的算法和數據結構,優化也不起作用。本小節後續的優化措施指的是後者。

5.1 函數調用和系統調用

(1) 從第四小節可以看出,函數調用的開銷不菲。所以在調用次數非常高的函數內,儘量少的使用函數,特別是小函數。

(2) 儘量把循環內部的函數調用移到循環外部。

帶來的副作用損害了代碼的可讀性,可維護性。

系統調用的的開銷:

1,段的切換。

2,當前數據、指令預取隊列的刷新和重建。

3,數據複製。

5.2 利用內存局部性原理

時間局部性:如果數據被訪問,則不久之後該數據可能被再次訪問。

空間局部性:如果某個存儲單元被訪問,則不久之後附近的存儲單元也會被訪問。

例:沒有利用空間局部性的代碼

for(i=0;i<M;i++)
for(j=0;j<N;j++)
    sum += a[j][i];

例:利用空間局部性的代碼

for(i=0;i<M;i++)
for(j=0;j<N;j++)
    sum += a[i][j];

重複利用同一變量有良好的時間局部性。

5.3 適應CPU的指令流水線

執行指令的基本流程:

1 從內存讀取指令到L1

2 對L1中的指令譯碼

3 執行單元執行指令

現代的CPU進行分支預測,在指令高速緩存區形成一個指令流水線,如果預測失敗,需要重新從內存讀取指令,譯碼形成一個新的指令流水線,對CPU而言,操作內存的開銷非常大(2個數量級的差別),所以代價非常高。

優化措施:

(1) 儘量減少分支

儘量不用if/else  switch。用switch代替if/else,因爲跳轉指令更少。

經過測試if 和?:的彙編代碼是一樣的,所以性能也一樣。

(2 )減少小循環,因爲循環裏面有跳轉指令

(3)減少函數調用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章