天天看點

58龍哥教你“如何做系統性能優化”(純幹貨)

58龍哥教你“如何做系統性能優化”(純幹貨)

如何做系統性能優化

性能優化的目标是什麼?不外乎兩個:

時間性能:減小系統執行的時間

空間性能:減小系統占用的空間

一、代碼優化

做代碼優化前,先了解下硬體Cache:

(1)Cache Level:通常來說L1、L2的Cache內建在CPU裡,L3的Cache放在CPU外;

(2)Cache Size:它決定你能把多少東西放到Cache裡,有Size就有競争,就有替換,才有所謂優化的空間;

(3)Cache Type:I-Cache(指令),D-Cache(資料),TLB(MMU的Cache);

代碼層次的優化主要從以下兩個角度考慮問題:

(1)I-Cache優化:精簡code path,簡化調用關系,減少備援代碼等等;

(2)D-Cache優化:減少D-Cache的miss數量,增加有效資料通路。

以下是一些技巧,可供參考:

(1)Code adjacency(把相關代碼放在一起)

這裡有兩層含義:一是相關源檔案要放在一起;二是相關函數在object檔案裡面,也應該相鄰。

這樣,可執行檔案被夾在到記憶體裡時,函數位置也是相鄰的,同僚還符合子產品化程式設計的要求:高内聚,低耦合。

(2)Cache line alignment(cache對齊)

對齊Cache以減少潛在的一次讀寫,但這可能意味着記憶體的浪費,需要從空間和時間兩方面衡量。

(3)Branch prediction(分支預測)

如果能預測那段代碼有更高的執行機率,就能減少跳轉次數(調整if和else的順序?)。

(4)Data prefetch(資料預取)

由CPU自動完成。

(5)Register parameters(寄存器參數)

(6)Lazy computation(延時計算)

最近不用的變量,不要急着去初始化(意味着可能執行複雜的構造),如果某個分支跳出了函數,這些動作就浪費了。

COW(copy-on-write)就是一種延時計算的技術。

(7)Early computation(提前計算)

有些變量,計算一次就夠了,任何加減乘除都會消耗CPU指令,盡量使用常數,而不是246060來表示一天的秒數。

(8)Inline(内聯函數)

(9)Macro(宏定義)

(10)Allocation on stack(局部變量)

避免在棧上申請大數組,其初始化和銷毀的代價很高。

(11)Per-cpu data structure(非共享資料結構)

避免共享量的鎖,在thread local裡,多核情況下使用局部變量會帶來好處。

(12)Reduce call path or call trace(減少函數調用層次)

(13)Read&write split(讀寫分離)

(14)Recude duplicated code(減少備援代碼)

其中,(1)(6)(7)(8)(9)(10)(11)(12)(14)這些優化方式最為常見。

二、工具優化

“工欲善其事,必先利其器”,如果沒有工具的支援,性能優化難以實施,誰也不知道哪些地方是嚴重影響性能的主要沖突。

使用性能優化的工具,需要考慮以下問題:

(1)使用工具是否需要重新執行編譯?

(2)工具本身對測量結果的影響:工具對性能的影響必須在一個可接受的範圍以内。

工具能解決的問題:

(1)建立性能基線,以作對比;

(2)幫助定位性能瓶頸;

(3)幫助驗證優化方案;

性能測試工具一般由這麼幾種:

(1)收集CPU的性能計數;

(2)利用編譯器的功能,在函數入口和出口加回調函數;

(3)在代碼中加入時間測量點。

在Linux下,通常會用到的有:

(1)Oprofile

它已經加入Linux核心代碼庫,但通常需要重新編譯核心,參考如下

http://oprofile.sourceforge.net/news/ http://people.redhat.com/wcohen/Oprofile.pdf

(2)KFT and Gprof

KFT是kernel的一個patch,隻對kernel有效;Gprof是gcc裡面的一個工具,隻對使用者空間的程式有效。這兩個工具都需要重新編譯代碼,它們都用到了gcc裡面的finstrument-functions選項。編譯時會在函數入口,出口加回調函數,而且inline函數也會改成非inline的。它的工作原理可以參考:

http://blog.linux.org.tw/~jserv/archives/001870.html http://blog.linux.org.tw/~jserv/archives/001723.html http://elinux.org/Kernel_Function_Trace http://www.network-theory.co.uk/docs/gccintro/gccintro_80.html

三、系統優化

從系統層面去優化系統往往有更為明顯的效果,優化之前,可以思考,是否能夠通過擴充系統來達到提高性能的目的:

(1)Scale up:使用更強的硬體;

(2)Scale out:使用更多的元件;

如果更新硬體的方法就能解決問題,為什麼還要使用修改代碼,調整架構這樣大風險的舉措呢?(需要考慮成本)

以下是一些常用的系統優化的方法:

(1)Cache

Cache幹什麼?儲存已經執行過的結果。

Cache為什麼有效?避免已計算過的開銷,擷取更快的通路。

Cache的難點在哪裡?一是快速比對;二是Cache容量有效,需要較好的替換政策;

Cache在哪些情況下有效?時間局部性。即目前計算的結果,後續有可能使用到,如果沒有時間局部性,反而對架構有害。

(2)Lazy Computing

理念就是,不要做多餘的事情,最常見的例子就是COW(copy-on-write):

http://en.wikipedia.org/wiki/Copy-on-write

COW幹什麼?寫時複制。

COW為什麼有效?節省記憶體複制時間,均勻記憶體配置設定時間。

COW的難點在哪裡?一是引用計數的使用;二是确認哪些記憶體是可以共享的。

(3)read ahead/pre-fetch預讀

http://en.wikipedia.org/wiki/Readahead

預讀幹什麼?提前準備所需要的資料。

預讀為什麼有效?減少等待記憶體的時間,相當于把多個操作集合成一個。

預讀在哪些情況下有效?空間局部性。

(4)Asynchronous異步

http://en.wikipedia.org/wiki/Asynchronous_I/O

異步幹什麼?異步是一種通信方式,請求與應答分離。

異步為什麼有效?消除等待時間。

異步的難點是什麼?如何實作分布式狀态機,如何使用回調,使異步時間到達後繼續執行。

異步在哪些情況下有效?狀态之間不能有強依賴關系。

(5)Polling輪詢

http://en.wikipedia.org/wiki/Polling_(computer_science)

(6)Static memory pool(記憶體池)

http://en.wikipedia.org/wiki/Static_memory_allocation

記憶體池幹什麼?提前配置設定記憶體以擷取更好的性能,隻是适應性可能會降低,并可能造成記憶體浪費。

記憶體池為什麼有效?避免重複記憶體申請、釋放開銷。

記憶體池的難點是什麼?配置設定多大的記憶體池,如何避免浪費都是需要考慮的問題。

記憶體池在哪些情況下有效?一是固定大小的記憶體需求,二是快速的配置設定與釋放需求。

四、總結

性能優化隻是系統的一個方面,它可能會和系統的其他要求有沖突,比如

可讀性:性能優化不能影響可讀性,誰願意維護不怎麼漂亮的代碼;

子產品化:性能優化往往需要打破子產品的邊界,想想這是否值得;

可移植:隔離硬體相關的代碼,盡量使用統一的API;

可維護:許多性能優化的技巧,會導緻後來維護代碼的人崩潰;

需要在性能優化和上述的幾個要求之間做出tradeoff,不能一意孤行。

繼續閱讀