性能優(yōu)化技巧之系統(tǒng)層次優(yōu)化
編者按:系統(tǒng)層次的優(yōu)化有很多方法,如利用Cache、 Lazy computing、Read ahead等等,對系統(tǒng)層次進行優(yōu)化,效果也是比較明顯的。本文將會討論一些常見的系統(tǒng)優(yōu)化的方法。
從系統(tǒng)層次去優(yōu)化系統(tǒng)往往有比較明顯的效果。但是,在優(yōu)化之前,我們先要問一問,能否通過擴展系統(tǒng)來達到提高性能的目的,比如:
- Scale up: 用更強的硬件替代當(dāng)前的硬件
- Scale out: 用更多的部件來增強系統(tǒng)的性能
使用更強的硬件當(dāng)然和優(yōu)化沒有半點關(guān)系,但是如果這是一個可以接受的方案,為什么不用這個簡單易行的方案哪?替換硬件的風(fēng)險要比改架構(gòu),改代碼的風(fēng)險小多了,何樂而不為?
Scale out的方案就有一點麻煩。它要求系統(tǒng)本身是支持scale out,或者把系統(tǒng)優(yōu)化成可以支持scale out。不管是哪一種選擇,都不是一個簡單的選擇。設(shè)計一個可以scale out的系統(tǒng)已經(jīng)超出了本文所要關(guān)注的范圍,但是,scale out應(yīng)該是系統(tǒng)優(yōu)化的一個重要方向。
下面會討論一些常見的系統(tǒng)優(yōu)化的方法,如果還有其他沒有提到的,也歡迎讀者指出來。
1) Cache
- Cache是什么?Cache保存了已經(jīng)執(zhí)行過的結(jié)果。
- Cache為什么有效?一是可以避免計算的開銷(比如SQL查詢的開銷);二是離計算單元更近,所以訪 問更快(比如CPU cache)。
- Cache的難點在哪里?一是快速匹配,這涉及到匹配算法選擇(一般用哈希表),Cache容量(哈希表的容量影響查找速度);二是替換策略(一般使用LRU或者隨機替換等等)。
- Cache在哪些情況下有效?毫無疑問,時間局部性,也就是當(dāng)前的結(jié)果后面會用到,如果沒有時間局部性,Cache就不能提高性能,反而對性能和系統(tǒng)架構(gòu)有害處。所以在系統(tǒng)設(shè)計之初,最好是審視一下數(shù)據(jù)流程,再決定是否引入Cache層。
2) Lazy computing
Lazy computing(延遲計算),簡而言之,就是不要做額外的事情,特別是無用的事情。最常見的一個例子就是COW(copy on write),可以參考這個鏈接http://en.wikipedia.org/wiki/Copy-on-write。
- COW是什么?寫時復(fù)制。也就是說fork進程時,子進程和父進程共享相同的代碼段和數(shù)據(jù)段,如果沒有寫的動作發(fā)生,就不要為子進程分配新的數(shù)據(jù)段(通常在fork之后,會有exec,用新的代碼段和數(shù)據(jù)段替換原來的代碼段和數(shù)據(jù)段,所以復(fù)制父進程的數(shù)據(jù)段是沒有用的)。
- COW為什么有效?一是可以節(jié)省復(fù)制內(nèi)存的時間,二是可以節(jié)省內(nèi)存分配的時間(到真正需要時再分配,雖然時間不會減少,但是CPU的使用更加均勻,避免抖動)。
- COW的難點在哪里?一是引用計數(shù),多個指針指向同一塊內(nèi)存,如果沒有引用計數(shù),內(nèi)存無法釋放;二是 如何知道哪塊內(nèi)存是可以共享的?(在fork的例子里面,父進程,子進程的關(guān)系非常明確,但是在有些應(yīng)用里面,需要查找能夠共享的內(nèi)存,查找需要花時間)
Lazy computing在哪些情況下有效?目前能想到的只有內(nèi)存復(fù)制。用時分配內(nèi)存算不算哪?用時分配內(nèi)存不能節(jié)省時間,但是可以節(jié)省空間。靜態(tài)內(nèi)存對時間性能有好處;動態(tài)內(nèi)存對空間性能有好處。就看目標(biāo)是優(yōu)化哪個性能了。
3) Read ahead
Read ahead (預(yù)讀),也可以稱之為pre-fetch(預(yù)取)。就是要提前準(zhǔn)備所需要的數(shù)據(jù),避免使用時的等待。
- Read ahead是什么?可以參考http://en.wikipedia.org/wiki/Readahead,這個是講文件預(yù)讀的。CPU里面也有pre-fetch(CPU預(yù)取需要仔細(xì)安排,最好是能夠填充流水線,所以需要多次嘗試才有結(jié)果)。
- Read ahead為什么有效?Read ahead可以減少等待內(nèi)存的時間。其實相當(dāng)于把多個讀的動作集合成一個。這個和網(wǎng)絡(luò)里面的buffering或者sliding window有異曲同工之妙。停-等協(xié)議是最簡單的,但是效率也最低。
- Read ahead的難點在哪里?預(yù)讀多少才合適?預(yù)讀窗口的大小需要根據(jù)負(fù)載,文件使用的多少等因素動態(tài)調(diào)整。預(yù)測的成功與否關(guān)系的性能。所以這并不是一個簡單的優(yōu)化方法。
- Read ahead在哪些情況下有效?毫無疑問,空間局部性。沒有空間局部性,read ahead就失去了用武之地。用錯了,反而會降低性能。
4) Hardware assist
Hardware assist (硬件輔助),顧名思義,就是用硬件實現(xiàn)某些功能。常見的,比如加密,解密;正則表達式或者DFA engine,或者規(guī)則查找,分類,壓縮,解壓縮等等。邏輯簡單,功能確定,CPU intensive的工作可以考慮用硬件來代替。
- Hardware assist為什么有效?協(xié)處理器可以減輕CPU的工作,而且速度比CPU做要快(這個要看情況,并不是任何情況下都成立)。Hardware assist和Hardware centric的設(shè)計完全不同,不能混為一談。在Hardware assist的設(shè)計里面,主要工作還是由軟件完成;而hardware centric就是基于ASIC的設(shè)計方案,大部分工作是有硬件來完成。
- Hardware assist的難點在哪里?一是采用同步還是異步的方式與硬件交互(通常是異步);二是如何使硬件滿負(fù)荷工作,同時又避免緩沖區(qū)溢出或丟棄(這個要安排好硬件和軟件的節(jié)奏,使之協(xié)調(diào)工作);還有就是硬件 訪問內(nèi)存的開銷(盡量硬件本身所帶的內(nèi)存,如果有的話)。
5) Asynchronous
Asynchronous(異步)。同步,異步涉及到消息傳遞。一般來說,同步比較簡單,性能稍低;而異步比較復(fù)雜,但是性能較高。
- Asynchronous是什么?異步的含義就是請求和應(yīng)答分離,請求和應(yīng)答可以由不同的進程或線程完成。比如在 TCP協(xié)議的實現(xiàn)里面,如果滑動窗口是1,那么每次只能發(fā)送一個字節(jié),然后等待應(yīng)答;如果增加滑動窗口,那么一次可以發(fā)送多個字節(jié),而無需等待前一個字節(jié)的應(yīng)答。這樣可以提高性能。
- Asyncrhonous為什么有效?異步消除了等待的時間,可以更有效利用帶寬。
- Asynchronous的難點是什么?一是如何實現(xiàn)分布式的狀態(tài)機?由于請求和應(yīng)答雙方是獨立的,所以要避免狀態(tài)之間有依賴關(guān)系,在無法消除狀態(tài)之間的依賴關(guān)系時,必須使用同步消息(比如三次握手);二是應(yīng)答來了之后, 如果激活原來的執(zhí)行過程,使之能夠繼續(xù)執(zhí)行。
- Asynchronous在哪些情況下有效?很明顯,狀態(tài)之間不能有依賴關(guān)系,同時需要足夠的帶寬(或者窗口)。
6) Polling
Polling(輪詢)。Polling是網(wǎng)絡(luò)設(shè)備里面常用的一個技術(shù),比如Linux的NAPI或者epoll。與之對應(yīng)的是中斷,或者是事件。
- Polling為什么有效?Polling避免了狀態(tài)切換的開銷,所以有更高的性能。
- Polling的難點是什么?如果系統(tǒng)里面有多種任務(wù),如何在polling的時候,保證其他任務(wù)的執(zhí)行時間?Polling 通常意味著獨占,此時系統(tǒng)無法響應(yīng)其他事件,可能會造成嚴(yán)重后果。
- Polling在哪些情況下有效?凡是能用事件或中斷的地方都能用polling替代,是否合理,需要結(jié)合系統(tǒng)的數(shù)據(jù)流程來決定。
7) Static memory pool
Static memory pool(靜態(tài)內(nèi)存)。如前所述,靜態(tài)內(nèi)存有更好的性能,但是適應(yīng)性較差(特別是系統(tǒng)里面有多個 任務(wù)的時候),而且會有浪費(提前分配,還沒用到就分配了)。
- Static memory pool為什么有效?它可以使內(nèi)存管理更加簡單,避免分配和是否內(nèi)存的開銷,并且有利于調(diào)試內(nèi)存問題。
- Static memory pool的難點在哪里?分配多大的內(nèi)存?如何避免浪費?如何實現(xiàn)O(1)的分配和釋放?如何初始化內(nèi)存?
- Static memory pool在哪些情況下有效?一是固定大小的內(nèi)存需求(通常與系統(tǒng)的capacity有關(guān)),內(nèi)存對象的大小一致,并且要求快速的分配和釋放。
系統(tǒng)層次的優(yōu)化應(yīng)該還有很多方法,能想起來的就這么多了(這部分比較難,醞釀了很久,才想起來這么一點東西^-^),讀者如果有更好的方法,可以一起討論。性能優(yōu)化是關(guān)注實踐的工作,任何紙上談兵都是瞎扯,與讀者共勉。
【編輯推薦】