国产麻豆剧传媒精品网站,中文乱码视亚洲,91精品乱码一区二区三区,亚洲水蜜桃久久综合网站,欧美黄色免费看,91欧美一区二区三区综合在线

二維碼
企資網(wǎng)

掃一掃關(guān)注

當(dāng)前位置: 首頁(yè) » 企資快報(bào) » 服務(wù) » 正文

工具人一文講清軟件姓能優(yōu)化

放大字體  縮小字體 發(fā)布日期:2022-06-08 06:03:34    作者:付蘭若    瀏覽次數(shù):19
導(dǎo)讀

性能優(yōu)化是指在不影響正確性得前提下,使程序運(yùn)行得更快,它是一個(gè)非常廣泛得話題。優(yōu)化有時(shí)候是為了降低成本,但有時(shí)候,性能能決定一個(gè)產(chǎn)品得成敗,比如服務(wù)器得團(tuán)戰(zhàn)玩法需要單服達(dá)到一定得同時(shí)在線

性能優(yōu)化是指在不影響正確性得前提下,使程序運(yùn)行得更快,它是一個(gè)非常廣泛得話題。

優(yōu)化有時(shí)候是為了降低成本,但有時(shí)候,性能能決定一個(gè)產(chǎn)品得成敗,比如服務(wù)器得團(tuán)戰(zhàn)玩法需要單服達(dá)到一定得同時(shí)在線人數(shù)才能支撐起這類玩法,而電信軟件得性能往往是競(jìng)標(biāo)得核心競(jìng)爭(zhēng)力,性能關(guān)乎商業(yè)成敗。

軟件產(chǎn)品多種多樣,影響程序執(zhí)行效率得因素很多,因此,性能優(yōu)化,特別是對(duì)不熟悉得項(xiàng)目做優(yōu)化,不是一件容易得事。

性能優(yōu)化可分為宏觀和微觀兩個(gè)層面。宏觀層面包括架構(gòu)重構(gòu),而微觀層面,則包括算法得優(yōu)化,編譯優(yōu)化,工具分析,高性能編碼等,這些方法是有可能獨(dú)立于具體業(yè)務(wù)邏輯,因而有更加廣泛得適應(yīng)性,且更易于實(shí)施。

具體到性能優(yōu)化得方法論,首先,應(yīng)建立度量,你度量什么,你得到什么。所以,性能優(yōu)化測(cè)試先行,須基于數(shù)據(jù)而不能憑空猜測(cè),這是做優(yōu)化得一個(gè)基本原則。搭建真實(shí)得壓測(cè)環(huán)境,或者逼近真實(shí)環(huán)境,有時(shí)候是困難得,也可能非常耗費(fèi)時(shí)間,但它依然是值得得。

有許多工具能幫助我們定位程序瓶頸,有些工具能做很友好得圖形化展示,定位問題是解決問題得前置條件,但定位問題可能不是最難得,分析和優(yōu)化才是最耗時(shí)得關(guān)鍵環(huán)節(jié),修改之后,要再回歸測(cè)試,驗(yàn)證是否如預(yù)期般有效。

什么是高性能程序?架構(gòu)致廣遠(yuǎn)、實(shí)現(xiàn)盡精微。

架構(gòu)優(yōu)化得關(guān)鍵是識(shí)別瓶頸,這類優(yōu)化有很多套路,比如通過負(fù)載均衡做分布式改造,比如用多線程協(xié)程做并行化改造,比如用消息隊(duì)列做異步化和解耦,比如用事件通知替代輪詢,比如為數(shù)據(jù)訪問增加緩存,比如用批處理+預(yù)取提升吞吐,比如IO與邏輯分離、讀寫分離等等。

架構(gòu)調(diào)整和優(yōu)化雖然收效很大,卻因受限于各種現(xiàn)實(shí)因素,因而并不總是可行。

能不做得盡量不做、必須做得高效做是性能優(yōu)化得一個(gè)根本法則,提升處理能力和降低計(jì)算量可視為性能優(yōu)化得兩個(gè)方向。

怎么讓程序跑得更快?這要求我們充分利用硬件得各種特性,想方設(shè)法減少等待并且提高并發(fā),提升CACHE命中率,使用更高效得結(jié)構(gòu)和算法;而降低計(jì)算量,則可能意味著要跳出純技術(shù)范疇,從產(chǎn)品和業(yè)務(wù)視角去審視:哪些功能是必須得,哪些功能是可選可配置得。

有時(shí)候,我們不得不從細(xì)節(jié)得維度去改進(jìn)程序。通常,我們應(yīng)該使用簡(jiǎn)單得數(shù)據(jù)結(jié)構(gòu)和算法,但如有必要,就應(yīng)積極使用更高效得結(jié)構(gòu)和算法,不止邏輯結(jié)構(gòu),物理結(jié)構(gòu)(實(shí)現(xiàn))同樣影響執(zhí)行效率;分支預(yù)測(cè)、反饋優(yōu)化、啟發(fā)性以及基于機(jī)器學(xué)習(xí)編譯優(yōu)化得效果日益凸顯;熟練掌握編程語言深刻理解標(biāo)準(zhǔn)庫(kù)實(shí)現(xiàn)能幫助我們規(guī)避低性能陷阱;深入細(xì)節(jié)做代碼微調(diào)甚至指令級(jí)優(yōu)化有時(shí)候也能取得意想不到得效果。

有時(shí)候,我們需要做一些交換,比如用空間置換時(shí)間,比如犧牲一些通用性可讀性換取高性能,我們只應(yīng)當(dāng)在非常必要得情況下才這么做,它是權(quán)衡得藝術(shù)。

## 1、架構(gòu)優(yōu)化

通常系統(tǒng)得throughput越大,latency就越高,但過高得latency不可接受,所以架構(gòu)優(yōu)化不是一味追求throughput,也需要latency,追求可接受latency下得高throughput。

### 負(fù)載均衡

負(fù)載均衡其實(shí)就是解決一個(gè)分活得問題,對(duì)應(yīng)到分布式系統(tǒng),一般在邏輯服得前面都會(huì)安放一個(gè)負(fù)載均衡器,比如NGINX就是經(jīng)典得解決方案。負(fù)載均衡不限于分布式系統(tǒng),對(duì)于多線程架構(gòu)得服務(wù)器內(nèi)部,也需要解決負(fù)載均衡得問題,讓各個(gè)worker線程得負(fù)載均衡。

### 多線程、協(xié)程并行化

雖然硬件架構(gòu)得復(fù)雜化對(duì)程序開發(fā)提出了更高得要求,但編寫充分利用多CPU多核特性得程序能獲得令人驚嘆得收益,所以,在同樣硬件規(guī)格下,基于多線程/協(xié)程得并行化改造依然值得嘗試。

多線程不可避免要面臨資源競(jìng)爭(zhēng)得問題,我們得設(shè)計(jì)目標(biāo)應(yīng)該是充分利用硬件多執(zhí)行核心得優(yōu)勢(shì),減少等待,讓多個(gè)執(zhí)行流暢快得奔跑起來。

對(duì)于多線程模型,如果把每一個(gè)要干得活抽象為一個(gè)task,把干活得線程抽象為worker,那么,有兩種典型得設(shè)計(jì)思路,一種是對(duì)task類型做出劃分,讓一類或者一個(gè)worker去干特定得task,另一種是讓所有worker去干所有task。

第壹種劃分,能減少數(shù)據(jù)爭(zhēng)用,編碼實(shí)現(xiàn)也更簡(jiǎn)單,只需要識(shí)別有限得競(jìng)爭(zhēng),就能讓系統(tǒng)工作得很好,缺點(diǎn)是任務(wù)得工作量很可能不同,有可能導(dǎo)致有些worker忙碌而另一些空閑。

第二種劃分,優(yōu)點(diǎn)是能均衡,缺點(diǎn)是編碼復(fù)雜性高,數(shù)據(jù)競(jìng)爭(zhēng)多。

有時(shí)候,我們會(huì)綜合上述兩種模式,比如讓單獨(dú)得線程去做IO(收發(fā)包)+反序列化(產(chǎn)生protocol task),然后啟動(dòng)一批worker線程去處理包,中間通過一個(gè)task queue去連接,這即是經(jīng)典得生產(chǎn)者消費(fèi)者模型。

協(xié)程是一種用戶態(tài)得多執(zhí)行流,它基于一個(gè)假設(shè),即用戶態(tài)得任務(wù)切換成本低于系統(tǒng)得線程切換。

### 通知替代輪詢

輪詢即不停詢問,就像你每隔幾分鐘去一趟宿管那里查看是否有信件,而通知是你告訴宿管阿姨,你有信得時(shí)候,她打電話通知你,顯然輪詢耗費(fèi)CPU,而通知機(jī)制效率更高。

### 添加緩存

緩存得理論依據(jù)是局部性原理。

一般系統(tǒng)得寫入請(qǐng)求遠(yuǎn)少于讀請(qǐng)求,針對(duì)寫少讀多得場(chǎng)景,很適合引入緩存集群。

在寫數(shù)據(jù)庫(kù)得時(shí)候同時(shí)寫一份數(shù)據(jù)到緩存集群里,然后用緩存集群來承載大部分得讀請(qǐng)求,因?yàn)榫彺婕汉苋菀鬃龅礁咝阅埽裕@樣得話,通過緩存集群,就可以用更少得機(jī)器資源承載更高得并發(fā)。

緩存得命中率一般能做到很高,而且速度很快,處理能力也強(qiáng)(單機(jī)很容易做到幾萬并發(fā)),是理想得解決方案。

CDN本質(zhì)上就是緩存,被用戶大量訪問得靜態(tài)資源緩存在CDN中是目前得通用做法。

### 消息隊(duì)列

消息隊(duì)列、消息中間件是用來做寫請(qǐng)求異步化,我們把數(shù)據(jù)寫入MessageQueue就認(rèn)為寫入完成,由MQ去緩慢得寫入DB,它能起到削峰填谷得效果。

消息隊(duì)列也是解耦得手段,它主要用來解決寫得壓力。

### IO與邏輯分離、讀寫分離

IO與邏輯分離,這個(gè)前面已經(jīng)講了。讀寫分離是一種數(shù)據(jù)庫(kù)應(yīng)對(duì)壓力得慣用措施,當(dāng)然,它也不僅限于DB。

### 批處理與數(shù)據(jù)預(yù)取

批處理是一種思想,分很多種應(yīng)用,比如多網(wǎng)絡(luò)包得批處理,是指把收到得包攢到一起,然后一起過一遍流程,這樣,一個(gè)函數(shù)被多次調(diào)用,或者一段代碼重復(fù)執(zhí)行多遍,這樣i-cache得局部性就很好,另外,如果這個(gè)函數(shù)或者一段里要訪問得數(shù)據(jù)被多次訪問,d-cache得局部性也能改善,自然能提升性能,批處理能增加吞吐,但通常會(huì)增大延遲。

另一個(gè)批處理思想得應(yīng)用是日志落盤,比如一條日志大概寫幾十個(gè)字節(jié),我們可以把它緩存起來,攢夠了一次寫到磁盤,這樣性能會(huì)更好,但這也帶來數(shù)據(jù)丟失得風(fēng)險(xiǎn),不過通常我們可以通過shm得方式規(guī)避這個(gè)風(fēng)險(xiǎn)。

指令預(yù)取是CPU自動(dòng)完成得,數(shù)據(jù)預(yù)取是一個(gè)很有技巧性得工作,數(shù)據(jù)預(yù)取得依據(jù)是預(yù)取得數(shù)據(jù)將在接下來得操作中用到,它符合空間局部性原理,數(shù)據(jù)預(yù)取可以填充流水線,降低訪存等待,但數(shù)據(jù)預(yù)取會(huì)侵害代碼,且并不總?cè)珙A(yù)期般有效。

哪怕你不增加預(yù)取代碼,硬件預(yù)取器也有可能幫你做預(yù)取,另外gcc也有編譯選項(xiàng),開啟它會(huì)在編譯階段自動(dòng)插入預(yù)取代碼,手動(dòng)增加預(yù)取代碼需要小心處理,時(shí)機(jī)得選擇很重要,最后一定要基于測(cè)試數(shù)據(jù),另外,即使預(yù)取表現(xiàn)很好,但代碼修改也有可能導(dǎo)致效果衰減,而且預(yù)取語句執(zhí)行本身也有開銷,只有預(yù)取得收益大于預(yù)取得開銷,且CACHE-MISS很高才是值得得。

## 2、算法優(yōu)化

數(shù)據(jù)量小得集合上遍歷查找即可,但如果循環(huán)得次數(shù)過百,便需要考慮用更快得查找結(jié)構(gòu)和算法替換蠻力遍歷,哈希表,紅黑樹,二分查找很常用。

### 哈希(HASH)

哈希也叫散列,是把任意長(zhǎng)度得輸入通過散列算法變換成固定長(zhǎng)度得輸出,該輸出就是散列值,也叫摘要。比如把一篇文章得內(nèi)容通過散列生成64位得摘要,該過程不可逆。

這種轉(zhuǎn)換是一種壓縮映射,也就是,散列值得空間通常遠(yuǎn)小于輸入得空間,不同得輸入可能會(huì)散列成相同得輸出,所以不可能從散列值來確定唯一得輸入值,但如果輸出得位數(shù)足夠,散列成相同輸出得概率非常非常小。

字符串得比較有時(shí)會(huì)成為消耗較大得操作,雖然strcmp或者memcpy得實(shí)現(xiàn)用到了很多加速和優(yōu)化技巧,但本質(zhì)上它還是逐個(gè)比較得方式。

字符串比較得一個(gè)改進(jìn)方案就是哈希,比較哈希值(通常是一個(gè)int64得整數(shù))而非比較內(nèi)容能快很多,但需要為字符串提前計(jì)算好哈希值,且需要額外得空間保存哈希值,另外,在哈希值相等得時(shí)候,還需要比較字符串,但因?yàn)闆_突得概率極低,所以后續(xù)得字符串比較不會(huì)很多次。

這樣不一定總是更高效,但它提供了另一個(gè)思路,你需要測(cè)試你得程序,再?zèng)Q定要不要這樣做。

另一個(gè)哈希得用法是哈希表,哈希表得經(jīng)典實(shí)現(xiàn)是提前開辟一些桶,通過哈希找到元素所在得桶(編號(hào)),如果沖突,再拉鏈解決沖突。

為了減少?zèng)_突經(jīng)常需要開辟更多得桶,但更多得桶需要更大得存儲(chǔ)空間,特別是元素?cái)?shù)量不確定得時(shí)候,桶得數(shù)量選擇變得兩難,隨著裝載得元素變多,沖突加劇,在擴(kuò)容得時(shí)候,將需要對(duì)已存在得元素重新哈希,這是很費(fèi)得點(diǎn)。

哈希表得沖突品質(zhì)不錯(cuò)情況下會(huì)退化成鏈表,當(dāng)初設(shè)想得快速查找變得不再可行,HashMap是普通哈希表得改進(jìn)版,結(jié)合哈希和二叉平衡搜索樹。

另一個(gè)常用來做查找得結(jié)構(gòu)是紅黑樹,它能確保最壞情況下,有l(wèi)ogN得時(shí)間復(fù)雜度,但紅黑樹得查找過程需要沿著鏈走,不同結(jié)點(diǎn)內(nèi)存通常不連續(xù),CACHE命中性經(jīng)常很差,紅黑樹得中序遍歷結(jié)果是有序得,這是哈希表不具備得,另外,紅黑樹不存在哈希表那般預(yù)估容量難得問題。

### 基于有序數(shù)組得二分查找

二分查找得時(shí)間復(fù)雜度也是logN,跟紅黑樹一致,但二分查找得空間局部性更好,不過二分查找有約束,它只能在有序數(shù)組上進(jìn)行,所以,如果你需要在固定得數(shù)據(jù)集合(比如配置數(shù)據(jù))做查找,二分查找是個(gè)不錯(cuò)得選擇。

### 跳表(Skip List)

跳表增加了向前指針,是一種多層結(jié)構(gòu)得有序鏈表,插入一個(gè)值時(shí)有一定概率晉升到上層形成間接得索引。

跳表是一個(gè)隨機(jī)化得數(shù)據(jù)結(jié)構(gòu),實(shí)質(zhì)就是一種可以進(jìn)行二分查找得有序鏈表。跳表在原有得有序鏈表上面增加了多級(jí)索引,通過索引來實(shí)現(xiàn)快速查找。跳表不僅能提高搜索性能,同時(shí)也可以提高插入和刪除操作得性能。

跳表適合大量并發(fā)寫得場(chǎng)景,可以認(rèn)為是隨機(jī)平衡得二叉搜索樹,不存在紅黑樹得再平衡問題。Redis強(qiáng)大得ZSet底層數(shù)據(jù)結(jié)構(gòu)就是哈希加跳表。

相比哈希表和紅黑樹,跳表用得不那么多。

### 數(shù)據(jù)結(jié)構(gòu)得實(shí)現(xiàn)優(yōu)化

我們通常只會(huì)講數(shù)據(jù)得邏輯結(jié)構(gòu),但數(shù)據(jù)得實(shí)現(xiàn)(存儲(chǔ))結(jié)構(gòu)也會(huì)影響性能。

數(shù)組在存儲(chǔ)上一定是邏輯地址連續(xù)得,但鏈表不具有這樣得特點(diǎn),鏈表通過鏈域?qū)ふ遗R近節(jié)點(diǎn),如果相鄰節(jié)點(diǎn)在地址上發(fā)散,則沿著鏈域訪問效率不高,所以實(shí)現(xiàn)上可以通過從單獨(dú)得內(nèi)存配置器分配結(jié)點(diǎn)(盡量?jī)?nèi)存收斂)來優(yōu)化訪問效率,同樣得方法也適應(yīng)紅黑樹、哈希表等其他結(jié)構(gòu)。

### 排序

盡量對(duì)指針、索引、排序,而不要對(duì)對(duì)象本身排序,因?yàn)榻粨Q對(duì)象比交換地址/索引慢;求topN不要做全排序;非穩(wěn)定排序能滿足要求不要搞穩(wěn)定排序。

### 延遲計(jì)算 & 寫時(shí)拷貝

延遲計(jì)算和寫時(shí)拷貝(COW)思想上是一樣得,即可以通過把計(jì)算盡量推遲來減少計(jì)算開銷。

我拿服務(wù)器開發(fā)來舉例,假設(shè)玩家得戰(zhàn)斗力(fight)是通過等級(jí),血量,稱號(hào)等其他屬性計(jì)算出來得,我們可以在等級(jí)、血量、稱號(hào)變化得時(shí)候立即重算fight,但血量可能變化比較頻繁,所以就會(huì)需要頻繁重算戰(zhàn)力。通過延遲計(jì)算,我們可以為戰(zhàn)力添加一個(gè)dirtyFlag,在等級(jí)、血量、稱號(hào)變化得時(shí)候設(shè)置dirtyFlag,等到真正需要用到戰(zhàn)力得時(shí)候(GetFight函數(shù))里判斷dirtyFlag,如果dirtyFlag為true則重算戰(zhàn)力并清dirtyFlag,如果dirtyFlag為false則直接返回fight值。

寫時(shí)拷貝(COW)跟這個(gè)差不多,linux kernel在fork進(jìn)程得時(shí)候,子進(jìn)程會(huì)共享父進(jìn)程得地址空間,只有在子進(jìn)程對(duì)自身地址空間寫得時(shí)候,才會(huì)clone一份出來,同樣,string得設(shè)計(jì)也用到了類似得思想。

### 預(yù)計(jì)算

有些值可以提前計(jì)算出結(jié)果并保存起來,不用重復(fù)計(jì)算得盡量不重復(fù)計(jì)算,特別是循環(huán)內(nèi)得計(jì)算,要避免重復(fù)得常量計(jì)算,C++甚至增加了一個(gè)constexpr得關(guān)鍵詞。

### 增量更新

增量更新得原理不復(fù)雜,只做增量,只做DIFF,不做全量,這個(gè)思想有很多應(yīng)用場(chǎng)景。

舉個(gè)例子,服務(wù)器每隔一段時(shí)間需要把玩家得屬性(比如血量、魔法值等)同步到客戶端,簡(jiǎn)單得做法是把所有屬性打包一次性全發(fā)送過去,這樣比較耗費(fèi)帶寬,可以考慮為每個(gè)屬性編號(hào),在發(fā)送得時(shí)候,只發(fā)送變化得屬性。

在發(fā)送端,編碼一個(gè)變化得屬性得時(shí)候,需要發(fā)送一個(gè)屬性編號(hào)+屬性值得對(duì)子,接收端類似,先解出屬性編號(hào),再解出屬性值,這種方式可能需要犧牲一點(diǎn)CPU換帶寬。

## 3、代碼優(yōu)化

### 內(nèi)存優(yōu)化

(a)小對(duì)象分配器

C得動(dòng)態(tài)內(nèi)存分配是介于系統(tǒng)和應(yīng)用程序得中間層,malloc/free本身體現(xiàn)得就是一種按需分配+復(fù)用得思想。

當(dāng)你調(diào)用malloc向glibc得動(dòng)態(tài)內(nèi)存分配器ptmalloc申請(qǐng)6字節(jié)得內(nèi)存,實(shí)際耗費(fèi)得會(huì)大于6字節(jié),6是動(dòng)態(tài)分配塊得有效載荷,動(dòng)態(tài)內(nèi)存分配器會(huì)為chunk添加首部和尾部,有時(shí)候還會(huì)加一下填充,所以,真正耗費(fèi)得存儲(chǔ)空間會(huì)遠(yuǎn)大于6字節(jié),在我得機(jī)器上,通過malloc_usable_size發(fā)現(xiàn)申請(qǐng)6字節(jié),返回得chunk,實(shí)際可用得size為24,加上首尾部就更多了。

但你真正申請(qǐng)(可用)得大小是6字節(jié),可見,動(dòng)態(tài)內(nèi)存分配得chunk內(nèi)有大量得碎片,這就是內(nèi)碎片,而外碎片是存在chunk之間得,是另一個(gè)問題。

當(dāng)你申請(qǐng)得size較大,有效載荷 / 耗費(fèi)空間得比例會(huì)比較高,內(nèi)碎片占比不高,但但size較小,這個(gè)占比就高,如果這種小size得chunk非常多,就會(huì)造成內(nèi)存得極大浪費(fèi)。

《C++設(shè)計(jì)新思維》一書中得loki庫(kù)實(shí)現(xiàn)了一個(gè)小對(duì)象分配器,通過隱式鏈表得方式解決了這個(gè)問題,有興趣得可以去看看。

(b)cached obj

《C++ Primer》實(shí)現(xiàn)了一個(gè)CachedObj類模板,任何需要擁有這種cached能力得類型都可以通過從CachedObj<T>派生而獲得。

它得主要思想是為該種類型維護(hù)一個(gè)FreeList,每個(gè)節(jié)點(diǎn)就是一個(gè)Object,對(duì)象申請(qǐng)得時(shí)候,檢查FreeList,如果FreeList不為空,則摘除頭結(jié)點(diǎn)返回,如果FreeList為空,則new一批Object并串到FreeList,然后走FreeList不為空得分配流程,通過重載類得operator new和operator delete,達(dá)到對(duì)類得使用者透明得目得。

(c)內(nèi)存分配和對(duì)象構(gòu)建分離

c得malloc用來動(dòng)態(tài)分配內(nèi)存,free用來歸還內(nèi)存;C++得new做了3件事,通過operator new(本質(zhì)上等同malloc)分配內(nèi)存,在分配得內(nèi)存上構(gòu)建對(duì)象,返回對(duì)象指針;而delete干了兩件事,調(diào)用析構(gòu)函數(shù),歸還內(nèi)存。

C++通過placement new可以分離內(nèi)存分配和對(duì)象構(gòu)建,結(jié)合顯示得析構(gòu)函數(shù)調(diào)用,達(dá)到自控得目得。

我優(yōu)化過一個(gè)項(xiàng)目,啟動(dòng)時(shí)間過長(zhǎng),記憶中需要幾十秒(至少十幾秒),分析后發(fā)現(xiàn)主要是因?yàn)閳?zhí)行預(yù)分配策略(對(duì)象池),在啟動(dòng)得時(shí)候按蕞大容量創(chuàng)建怪和玩家,對(duì)象構(gòu)建很重,大量對(duì)象構(gòu)建耗時(shí)過長(zhǎng),通過分離內(nèi)存分配和對(duì)象構(gòu)建,把對(duì)象構(gòu)建推遲到真正需要得時(shí)候,實(shí)現(xiàn)了服務(wù)得重啟秒起。

(d)內(nèi)存復(fù)用

編解碼、加解密、序列化反序列化(marshal/unmarshal)得時(shí)候一般都需要?jiǎng)討B(tài)申請(qǐng)內(nèi)存,這種調(diào)用頻次很高,可以考慮用靜態(tài)內(nèi)存,為了避免多線程競(jìng)爭(zhēng),可以用thread local。

當(dāng)然你也可以改進(jìn)靜態(tài)內(nèi)存策略,比如封裝一個(gè)GetEncodeMemeory(size_t)函數(shù),維護(hù)一個(gè)void* + size_t結(jié)構(gòu)體對(duì)象(初始化為NULL+0),對(duì)比參數(shù)size跟對(duì)象得size成員,如果參數(shù)size<=對(duì)象size,直接返回對(duì)象大得void*指針,否則free掉void*指針,再按參數(shù)size分配一個(gè)更大得void*,并用參數(shù)size更新對(duì)象size。

### cache優(yōu)化

i-cache優(yōu)化:i-cache得優(yōu)化可以通過精簡(jiǎn)code path,簡(jiǎn)化調(diào)用關(guān)系,減少代碼量,減少?gòu)?fù)雜度來實(shí)現(xiàn)。

具體措施包括,減少函數(shù)調(diào)用(就地展開、inline),利用分支預(yù)測(cè),減少函數(shù)指針,可以考慮把code path上相關(guān)得函數(shù)定義在一起,把相關(guān)得函數(shù)定義到一個(gè)源文件,并讓它們?cè)谠次募吓R近,這樣生成得object文件,在運(yùn)行時(shí)加載后相關(guān)函數(shù)大概率也內(nèi)存臨近,當(dāng)然編譯器也一直在做這方面得努力,但我們寫代碼不應(yīng)該依賴編譯器優(yōu)化,盡量去幫助編譯器生成更高效得代碼。

d-cache優(yōu)化:d-cache優(yōu)化包括改進(jìn)數(shù)據(jù)結(jié)構(gòu)和算法獲取更好得數(shù)據(jù)訪問時(shí)空局部性,比如二分查找就是d-cache友好算法。一個(gè)cache line一般是64B,如果數(shù)據(jù)跨越兩個(gè)cache-line,則會(huì)導(dǎo)致load & store2次,所以,需要結(jié)合cache對(duì)齊,盡量讓相關(guān)訪問得數(shù)據(jù)在一個(gè)cache-line。

如果結(jié)構(gòu)體過大,則各成員不僅可能在不同cache-line,甚至可能在不同page,所以應(yīng)該避免結(jié)構(gòu)體過大。

如果結(jié)構(gòu)體得成員變量過多,一般而言對(duì)各成員得訪問頻次也會(huì)滿足2-8定律,可以考慮把hot和cold得成員分開,重排結(jié)構(gòu)體成員變量順序,但這些騷操作我不建議在開始得時(shí)候用,因?yàn)檎f不定哪天又要增刪成員,從而破壞苦心孤詣搭建得積木。

### 判斷前置

判斷前置指在函數(shù)中講判斷返回得語句前置,這樣不至于忙活半天,你跟我說對(duì)不起不合適,要杜絕這種騙pao得做法。

在寫多個(gè)判斷得時(shí)候,把不滿足可能性高得放在前面。

在寫條件或得時(shí)候把為true得放在前面,在寫條件與得時(shí)候把為false得放在前面。

另外,如果在循環(huán)里調(diào)用一個(gè)函數(shù),而這個(gè)函數(shù)里檢查某條件,不符合就返回,這種情況,可以考慮把檢查放到調(diào)用函數(shù)得外面,這樣不滿足得話就不用陷入函數(shù),當(dāng)然,你也可以說,這樣得操作違背軟件工程,但看你想要什么,你不總是能夠兩全其美,對(duì)吧?

### 湊零為整與化整為零

湊零為整其實(shí)得思想在日志批處理里提了,不再展開。

化整為零體現(xiàn)了分而治之得思想,可以把一個(gè)大得操作,分?jǐn)傞_來,避免在做大操作得時(shí)候?qū)е驴D,從而讓CPU占比更加平穩(wěn)。

### 分頻

之前我優(yōu)化過一個(gè)服務(wù)器,服務(wù)器得邏輯線程是一個(gè)大循環(huán),里面調(diào)用tick函數(shù),tick函數(shù)里調(diào)用了所有需要check timer & do得事情,然后所有需要check timer & do得事情都塞進(jìn)tick里。

改進(jìn):tick里調(diào)用了tick50ms、tick100ms、tick500ms,tick1000ms,tick5000ms,然后把需要check timer & do得邏輯根據(jù)精度要求塞到不同得tickXXms里去。

### 減法

減少冗余

減少拷貝、零拷貝

減少參數(shù)個(gè)數(shù)(寄存器參數(shù)、取決于ABI約定)

減少函數(shù)調(diào)用次數(shù)/層次

減少存儲(chǔ)引用次數(shù)

減少無效初始化和重復(fù)賦值

### 循環(huán)優(yōu)化

這方面得知識(shí)很多,感覺一下子講不完,提幾點(diǎn),循環(huán)套循環(huán)要內(nèi)大外小,盡量把邏輯提取到循環(huán)外。

提取與循環(huán)無關(guān)得表達(dá)式,盡量減少循環(huán)內(nèi)不必要計(jì)算。

循環(huán)內(nèi)盡量使用局部變量。

循環(huán)展開是一種程序變換,通過增加每次迭代計(jì)算得元素得數(shù)量,減少循環(huán)得迭代次數(shù)。

還有循環(huán)分塊得騷操作。

### 防御性編程適可而止

有兩個(gè)流派,一個(gè)是完全得不信任,即所有函數(shù)調(diào)用里都對(duì)參數(shù)判斷,包括判空,有效性檢查等,但這樣做有幾點(diǎn)不好:

第壹,它只是貌似更安全,并不是真得更安全。

第二,它稀釋代碼濃度,淹沒關(guān)鍵語句。

第三,如果通過返回值報(bào)告錯(cuò)誤,則加重了調(diào)用者負(fù)擔(dān),調(diào)用者需要添加額外代碼檢查,不然更奇怪。

第四,重復(fù)判斷空耗CPU。

第五,埋雷,把本該crash或者暴露得問題埋得更深。

但這種做法大行其道,它有一定得市場(chǎng)和道理。

另一個(gè)是界定邊界,區(qū)分公開接口和內(nèi)部實(shí)現(xiàn),檢查只在模塊之間進(jìn)行,就相當(dāng)于進(jìn)園區(qū)得時(shí)候,門衛(wèi)會(huì)檢查你證件,但之后,則不再檢查。因?yàn)閮?nèi)部實(shí)現(xiàn)是受控得安全上下文,開發(fā)者應(yīng)該完全cover住。

我主張防御性編程適可而止,但現(xiàn)實(shí)中,軟件開發(fā)通常多人合作,每個(gè)開發(fā)者素質(zhì)不一樣,這就是客觀現(xiàn)實(shí),所以我也理解前一種做法。

### release干凈

開發(fā)過程中,我們會(huì)加很多診斷信息,比如我們可能接管內(nèi)存分配,從而附加額外得首尾部,通過填寫magic Num捕獲異常或者內(nèi)存越界,但這些信息應(yīng)該只用于開發(fā)階段得DEBUG需要,在release階段應(yīng)該通過預(yù)處理得方式刪除掉。

日志分級(jí)其實(shí)也體現(xiàn)了這種思想,通常有兩種做法,一個(gè)是定義級(jí)別變量,另一個(gè)是預(yù)處理,預(yù)處理干凈,但需要重新編譯生成image,而變量更靈活,但變量得比較還是有開銷得。

不要忽視這些診斷調(diào)試信息得開銷,牢記不必做得事情絕不做得原則。

### 慎用遞歸

遞歸得寫法簡(jiǎn)單,理解起來也容易,但遞歸是函數(shù)調(diào)用,有棧幀建立撤銷控制跳轉(zhuǎn)得開銷,另外也有爆棧得風(fēng)險(xiǎn),在性能敏感關(guān)鍵路徑,優(yōu)先考慮用非遞歸版本。

## 4、編譯優(yōu)化(寫不動(dòng)了)

### inline

### restrict

### LTO

### PGO

### 優(yōu)化選項(xiàng)

## 5、其他優(yōu)化(不想寫了)

### 綁核

### SIMD

### 鎖與并發(fā)

#### 鎖得粒度

#### 無鎖編程

#### Per-cpu data structure & thread local

#### 內(nèi)存屏障

#### 異構(gòu)優(yōu)化/TCO優(yōu)化

比如用GPGPU、FPGA、SmartNIC來offload原來cpu得任務(wù),TCO優(yōu)化指得是不以性能優(yōu)化為單一指標(biāo),而是在滿足性能條件下以綜合成本為優(yōu)化,當(dāng)然異構(gòu)也包括主動(dòng)利用CPU得avx或者其他邏輯單元,這類優(yōu)化往往編譯器不能自動(dòng)展開(等zrg)

常識(shí)和數(shù)據(jù)

CPU拷貝數(shù)據(jù)一般一秒鐘能做到幾百兆,當(dāng)然每次拷貝得數(shù)據(jù)長(zhǎng)度不同,吞吐不同。

一次函數(shù)執(zhí)行如果耗費(fèi)超過1000 cycles就比較大了(刨除調(diào)用子函數(shù)得開銷)。

pthread_mutex_t是futex實(shí)現(xiàn),不用每次都進(jìn)入內(nèi)核,首次加解鎖大概耗時(shí)4000-5000 cycles左右,之后,每次加解鎖大概120 cycles,O2優(yōu)化得時(shí)候100 cycles,spinlock耗時(shí)略少。

lock內(nèi)存總線+xchg需要50 cycles,一次內(nèi)存屏障要50 cycles。

有一些無鎖得技術(shù),比如CAS,比如linux kernel里得kfifo,主要利用了整型回繞+內(nèi)存屏障。

幾個(gè)如何?

1. 如何定位CPU瓶頸?

CPU是通常大家最先得性能指標(biāo),宏觀維度有核得CPU使用率,微觀有函數(shù)得CPU cycle數(shù),根據(jù)性能得模型,性能規(guī)格與CPU使用率是互相關(guān)聯(lián)得,規(guī)格越高,CPU使用率越高,但是處理器得性能往往又受到內(nèi)存帶寬、Cache、發(fā)熱等因素得影響,所以CPU使用率和規(guī)格參數(shù)之間并不是簡(jiǎn)單得線性關(guān)系,所以性能規(guī)格翻倍并不能簡(jiǎn)單地翻譯成我們得CPU使用率要優(yōu)化一倍。

至于CPU瓶頸得定位工具,最有名也是最有用得工具就是perf,它是性能分析得第壹步,可以幫我們找到系統(tǒng)得熱點(diǎn)函數(shù)。就像人看病一樣,只知道癥狀是不夠得,需要通過醫(yī)療機(jī)器進(jìn)一步分析病因,才能對(duì)癥下藥。

所以我們通過性能分析工具PMU或者其他工具去進(jìn)一步分析CPU熱點(diǎn)得原因比如是指令數(shù)本身就比較多,還是Cache miss導(dǎo)致得等,這樣在做性能優(yōu)化得時(shí)候不會(huì)走偏。

優(yōu)化CPU得目標(biāo)就是讓CPU運(yùn)行不受阻礙。

2. 如何定位IO瓶頸?

系統(tǒng)IO得瓶頸可以通過CPU和負(fù)載得非線性關(guān)系體現(xiàn)出來。當(dāng)負(fù)載增大時(shí),系統(tǒng)吞吐量不能有效增大,CPU不能線性增長(zhǎng),其中一種可能是IO出現(xiàn)阻塞。

系統(tǒng)得隊(duì)列長(zhǎng)度特別是發(fā)送、寫磁盤線程得隊(duì)列長(zhǎng)度也是IO瓶頸得一個(gè)間接指標(biāo)。

對(duì)于網(wǎng)絡(luò)系統(tǒng)來講,我建議先從外部觀察系統(tǒng)。所謂外部觀察是指通過觀察外部得網(wǎng)絡(luò)報(bào)文交換,可以用tcpdump, wireshark等工具,抓包看一下。

比如我們優(yōu)化一個(gè)RPC項(xiàng)目,它得吞吐量是10TPS,客戶希望是100TPS。我們使用wireshark抓取TCP報(bào)文流,可以分析報(bào)文之間得時(shí)間戳,響應(yīng)延遲等指標(biāo)來判斷是否是由網(wǎng)絡(luò)引起來得。

然后可以通過netstat -i/-s選項(xiàng)查看網(wǎng)絡(luò)錯(cuò)誤、重傳等統(tǒng)計(jì)信息。還可以通過iostat查看cpu等待IO得比例。IO得概念也可以擴(kuò)展到進(jìn)程間通信。

對(duì)于磁盤類得應(yīng)用程序,我們最希望看到寫磁盤有沒有時(shí)延、頻率如何。其中一個(gè)方法就是通過內(nèi)核ftrace、perf-event事件來動(dòng)態(tài)觀測(cè)系統(tǒng)。比如記錄寫塊設(shè)備得起始和返回時(shí)間,這樣我們就可以知道磁盤寫是否有延時(shí),也可以統(tǒng)計(jì)寫磁盤時(shí)間耗費(fèi)分布。有一個(gè)開源得工具包perf-tools里面包含著iolatency, iosnoop等工具。

3. 如何定位IO瓶頸?

應(yīng)用程序常用得IO有兩種:Disk IO和網(wǎng)絡(luò)IO。判斷系統(tǒng)是否存在IO瓶頸可以通過觀測(cè)系統(tǒng)或進(jìn)程得CPU得IO等待比例來進(jìn)行,比如使用mpstat、top命令。

系統(tǒng)得隊(duì)列長(zhǎng)度特別是發(fā)送、寫磁盤線程得隊(duì)列長(zhǎng)度也是IO瓶頸得一個(gè)重要指標(biāo)。

對(duì)于網(wǎng)絡(luò) IO來講,我們可以先使用netstat -i/-s查看網(wǎng)絡(luò)錯(cuò)誤、重傳等統(tǒng)計(jì)信息,然后使用sar -n DEV 1和sar -n TCP,ETCP 1查看網(wǎng)路實(shí)時(shí)得統(tǒng)計(jì)信息。ss (Socket Statistics)工具可以提供每個(gè)socket相關(guān)得隊(duì)列、緩存等詳細(xì)信息。

更直接得方法可以用tcpdump, wireshark等工具,抓包看一下。

對(duì)于Disk IO,我們可以通過iostat -x -p xxx來查看具體設(shè)備使用率和讀寫平均等待時(shí)間。如果使用率接近百分百,或者等待時(shí)間過長(zhǎng),都說明Disk IO出現(xiàn)飽和。

一個(gè)更細(xì)致得觀察方法就是通過內(nèi)核ftrace、perf-event來動(dòng)態(tài)觀測(cè)Linux內(nèi)核。比如記錄寫塊設(shè)備得起始和返回時(shí)間,這樣我們就可以知道磁盤寫是否有延時(shí),也可以統(tǒng)計(jì)寫磁盤時(shí)間耗費(fèi)分布。有一個(gè)開源得工具包perf-tools里面包含著iolatency, iosnoop等工具。

4.如何定位鎖得問題?

大家都知道鎖會(huì)引入額外開銷,但鎖得開銷到底有多大,估計(jì)很多人沒有實(shí)測(cè)過,我可以給一個(gè)數(shù)據(jù),一般單次加解鎖100 cycles,spinlock或者cas更快一點(diǎn)。

使用鎖得時(shí)候,要注意鎖得粒度,但鎖得粒度也不是越小越好,太大會(huì)增加撞鎖得概率,太小會(huì)導(dǎo)致代碼更難寫。

多線程場(chǎng)景下,如果cpu利用率上不去,而系統(tǒng)吞吐也上不去,那就有可能是鎖導(dǎo)致得性能下降,這個(gè)時(shí)候,可以觀察程序得sys cpu和usr cpu,這個(gè)時(shí)候通過perf如果發(fā)現(xiàn)lock得開銷大,那就沒錯(cuò)了。

如果程序卡住了,可以用pstack把堆棧打出來,定位死鎖得問題。

5. 如何提?Cache利用率?

內(nèi)存/Cache問題是我們常見得負(fù)載瓶頸問題,通常可利用perf等一些通用工具來幫助分析,優(yōu)化cache得思想可以從兩方面來著手,一個(gè)是增加局部數(shù)據(jù)/代碼得連續(xù)性,提升cacheline得利用率,減少cache miss,另一個(gè)是通過prefetch,降低miss帶來得開銷。

通過對(duì)數(shù)據(jù)/代碼根據(jù)冷熱進(jìn)行重排分區(qū),可提升cacheline得有效利用率,當(dāng)然觸發(fā)false-sharing另當(dāng)別論,這個(gè)需要根據(jù)運(yùn)行trace進(jìn)行深入調(diào)整了;說到prefetch,用過得人往往都有一種體會(huì),現(xiàn)實(shí)效果比預(yù)期差得比較遠(yuǎn),確實(shí)無論是數(shù)據(jù)prefetch還是代碼prefetch,不確定性太大,指望編譯器更靠譜點(diǎn)。

小結(jié)

性能優(yōu)化是一項(xiàng)細(xì)致得工作,工程師們?cè)铝τ趯ふ乙粍谟酪萁鉀Q性能問題得捷徑,但遺憾得是,沒有銀彈,但這并不意味著性能優(yōu)化無章可循。軟件工程師們?cè)谛阅軆?yōu)化方面積累了大量得經(jīng)驗(yàn),包括架構(gòu)、緩存、預(yù)取、工具、編譯器與編程語言,代碼重構(gòu)等實(shí)踐經(jīng)驗(yàn)方方面面,這些方法和探討都具有借鑒意義。
性能優(yōu)化也是一個(gè)系統(tǒng)性工程,出現(xiàn)性能瓶頸再優(yōu)化是一種先污染后治理得思路。更好得方式是將性能貫穿于軟件得整個(gè)生命周期之中,在設(shè)計(jì)之初即把性能作為一項(xiàng)需求甚至關(guān)鍵目標(biāo)加以考慮,開發(fā)中持續(xù)監(jiān)控性能得變化并嚴(yán)格遵從高性能編碼規(guī)范,后期維護(hù)將性能納入維護(hù)體系。

嚴(yán)格得說,性能優(yōu)化和性能設(shè)計(jì)有所不同,性能優(yōu)化通常是在現(xiàn)有系統(tǒng)和代碼基礎(chǔ)上做改進(jìn),它并非推倒重來,考驗(yàn)得是開發(fā)者反向修復(fù)得能力,而性能設(shè)計(jì)考驗(yàn)得是設(shè)計(jì)者得正向設(shè)計(jì)能力,但性能優(yōu)化得方法可以指導(dǎo)性能設(shè)計(jì),兩者互補(bǔ)。

最后,感謝陪伴,祝電池人新年快樂!

 
(文/付蘭若)
免責(zé)聲明
本文僅代表作發(fā)布者:付蘭若個(gè)人觀點(diǎn),本站未對(duì)其內(nèi)容進(jìn)行核實(shí),請(qǐng)讀者僅做參考,如若文中涉及有違公德、觸犯法律的內(nèi)容,一經(jīng)發(fā)現(xiàn),立即刪除,需自行承擔(dān)相應(yīng)責(zé)任。涉及到版權(quán)或其他問題,請(qǐng)及時(shí)聯(lián)系我們刪除處理郵件:weilaitui@qq.com。
 

Copyright ? 2016 - 2025 - 企資網(wǎng) 48903.COM All Rights Reserved 粵公網(wǎng)安備 44030702000589號(hào)

粵ICP備16078936號(hào)

微信

關(guān)注
微信

微信二維碼

WAP二維碼

客服

聯(lián)系
客服

聯(lián)系客服:

在線QQ: 303377504

客服電話: 020-82301567

E_mail郵箱: weilaitui@qq.com

微信公眾號(hào): weishitui

客服001 客服002 客服003

工作時(shí)間:

周一至周五: 09:00 - 18:00

反饋

用戶
反饋

主站蜘蛛池模板: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |