Go性能優(yōu)化1期

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

1.1 小對象合并成結(jié)構(gòu)體一次分配厘唾,減少內(nèi)存分配次數(shù)

做過C/C++的同學(xué)可能知道阅嘶,小對象在堆上頻繁地申請釋放载迄,會造成內(nèi)存碎片(有的叫空洞)护昧,導(dǎo)致分配大的對象時(shí)無法申請到連續(xù)的內(nèi)存空間,一般建議是采用內(nèi)存池捣炬。Go runtime底層也采用內(nèi)存池绽榛,但每個(gè)span大小為4k灭美,同時(shí)維護(hù)一個(gè)cache。cache有一個(gè)0到n的list數(shù)組铁坎,list數(shù)組的每個(gè)單元掛載的是一個(gè)鏈表,鏈表的每個(gè)節(jié)點(diǎn)就是一塊可用的內(nèi)存扩所,同一鏈表中的所有節(jié)點(diǎn)內(nèi)存塊都是大小相等的祖屏;但是不同鏈表的內(nèi)存大小是不等的买羞,也就是說list數(shù)組的一個(gè)單元存儲的是一類固定大小的內(nèi)存塊,不同單元里存儲的內(nèi)存塊大小是不等的魁兼。這就說明cache緩存的是不同類大小的內(nèi)存對象咐汞,當(dāng)然想申請的內(nèi)存大小最接近于哪類緩存內(nèi)存塊時(shí)儒鹿,就分配哪類內(nèi)存塊。當(dāng)cache不夠再向spanalloc中分配植阴。

建議:小對象合并成結(jié)構(gòu)體一次分配掠手,示意如下:


for k, v := range m {

    k, v := k, v // copy for capturing by the goroutine

    go func() {

        // using k & v

    }()

}

替換為:


for k, v := range m {

    x := struct {k , v string} {k, v} // copy for capturing by the goroutine

    go func() {

        // using x.k & x.v

    }()

}

1.2 緩存區(qū)內(nèi)容一次分配足夠大小空間狸捕,并適當(dāng)復(fù)用

在協(xié)議編解碼時(shí)灸拍,需要頻繁地操作[]byte,可以使用bytes.Buffer或其它byte緩存區(qū)對象混槐。

建議:bytes.Buffert等通過預(yù)先分配足夠大的內(nèi)存轩性,避免當(dāng)Grow時(shí)動(dòng)態(tài)申請內(nèi)存,這樣可以減少內(nèi)存分配次數(shù)。同時(shí)對于byte緩存區(qū)對象考慮適當(dāng)?shù)貜?fù)用舒岸。

1.3 slice和map采make創(chuàng)建時(shí)蛾派,預(yù)估大小指定容量

slice和map與數(shù)組不一樣,不存在固定空間大小眯杏,可以根據(jù)增加元素來動(dòng)態(tài)擴(kuò)容壳澳。

slice初始會指定一個(gè)數(shù)組巷波,當(dāng)對slice進(jìn)行append等操作時(shí),當(dāng)容量不夠時(shí)锉屈,會自動(dòng)擴(kuò)容:

如果新的大小是當(dāng)前大小2倍以上颈渊,則容量增漲為新的大兄辗稹;

否而循環(huán)以下操作:如果當(dāng)前容量小于1024乌询,按2倍增加妹田;否則每次按當(dāng)前容量1/4增漲装蓬,直到增漲的容量超過或等新大小霜浴。

map的擴(kuò)容比較復(fù)雜,每次擴(kuò)容會增加到上次容量的2倍晌纫。它的結(jié)構(gòu)體中有一個(gè)buckets和oldbuckets锹漱,用于實(shí)現(xiàn)增量擴(kuò)容:

正常情況下,直接使用buckets哥牍,oldbuckets為空毕泌;

如果正在擴(kuò)容,則oldbuckets不為空嗅辣,buckets是oldbuckets的2倍撼泛,

建議:初始化時(shí)預(yù)估大小指定容量

m := make(map[string]string, 100)

s := make([]string, 0, 100) // 注意:對于slice make時(shí),第二個(gè)參數(shù)是初始大小澡谭,第三個(gè)參數(shù)才是容量

1.4 長調(diào)用棧避免申請較多的臨時(shí)對象

goroutine的調(diào)用棧默認(rèn)大小是4K(1.7修改為2K)愿题,它采用連續(xù)棧機(jī)制,當(dāng)椡芙保空間不夠時(shí)抠忘,Go runtime會不動(dòng)擴(kuò)容:

當(dāng)棧空間不夠時(shí)崎脉,按2倍增加,原有棧的變量崆直接copy到新的棽ィ空間囚灼,變量指針指向新的空間地址;

退棧會釋放椉礼茫空間的占用灶体,GC時(shí)發(fā)現(xiàn)棧空間占用不到1/4時(shí)掐暮,則椥椋空間減少一半。

比如棧的最終大小2M路克,則極端情況下樟结,就會有10次的擴(kuò)棧操作,這會帶來性能下降精算。

建議:

控制調(diào)用棧和函數(shù)的復(fù)雜度瓢宦,不要在一個(gè)goroutine做完所有邏輯;

如查的確需要長調(diào)用棧灰羽,而考慮goroutine池化驮履,避免頻繁創(chuàng)建goroutine帶來?xiàng)鱼辙?臻g的變化。

1.5 避免頻繁創(chuàng)建臨時(shí)對象

Go在GC時(shí)會引發(fā)stop the world玫镐,即整個(gè)情況暫停倒戏。雖1.7版本已大幅優(yōu)化GC性能,1.8甚至量壞情況下GC為100us恐似。但暫停時(shí)間還是取決于臨時(shí)對象的個(gè)數(shù)杜跷,臨時(shí)對象數(shù)量越多,暫停時(shí)間可能越長蹂喻,并消耗CPU。

建議:GC優(yōu)化方式是盡可能地減少臨時(shí)對象的個(gè)數(shù):

盡量使用局部變量

所多個(gè)局部變量合并一個(gè)大的結(jié)構(gòu)體或數(shù)組捂寿,減少掃描對象的次數(shù)口四,一次回盡可能多的內(nèi)存。

2 并發(fā)優(yōu)化

2.1 高并發(fā)的任務(wù)處理使用goroutine池

goroutine雖輕量秦陋,但對于高并發(fā)的輕量任務(wù)處理蔓彩,頻繁來創(chuàng)建goroutine來執(zhí)行,執(zhí)行效率并不會太高效:

過多的goroutine創(chuàng)建驳概,會影響go runtime對goroutine調(diào)度赤嚼,以及GC消耗;

高并時(shí)若出現(xiàn)調(diào)用異常阻塞積壓顺又,大量的goroutine短時(shí)間積壓可能導(dǎo)致程序崩潰更卒。

2.2 避免高并發(fā)調(diào)用同步系統(tǒng)接口

goroutine的實(shí)現(xiàn),是通過同步來模擬異步操作稚照。在如下操作操作不會阻塞go runtime的線程調(diào)度:

網(wǎng)絡(luò)IO

channel

time.sleep

基于底層系統(tǒng)異步調(diào)用的Syscall

下面阻塞會創(chuàng)建新的調(diào)度線程:

本地IO調(diào)用

基于底層系統(tǒng)同步調(diào)用的Syscall

CGo方式調(diào)用C語言動(dòng)態(tài)庫中的調(diào)用IO或其它阻塞

網(wǎng)絡(luò)IO可以基于epoll的異步機(jī)制(或kqueue等異步機(jī)制)蹂空,但對于一些系統(tǒng)函數(shù)并沒有提供異步機(jī)制。例如常見的posix api中果录,對文件的操作就是同步操作上枕。雖有開源的fileepoll來模擬異步文件操作。但Go的Syscall還是依賴底層的操作系統(tǒng)的API弱恒。系統(tǒng)API沒有異步辨萍,Go也做不了異步化處理。

建議:把涉及到同步調(diào)用的goroutine返弹,隔離到可控的goroutine中锈玉,而不是直接高并的goroutine調(diào)用。

2.3 高并發(fā)時(shí)避免共享對象互斥

傳統(tǒng)多線程編程時(shí)义起,當(dāng)并發(fā)沖突在4~8線程時(shí)嘲玫,性能可能會出現(xiàn)拐點(diǎn)。Go中的推薦是不要通過共享內(nèi)存來通訊并扇,Go創(chuàng)建goroutine非常容易去团,當(dāng)大量goroutine共享同一互斥對象時(shí),也會在某一數(shù)量的goroutine出在拐點(diǎn)。

建議:goroutine盡量獨(dú)立土陪,無沖突地執(zhí)行昼汗;若goroutine間存在沖突,則可以采分區(qū)來控制goroutine的并發(fā)個(gè)數(shù)鬼雀,減少同一互斥對象沖突并發(fā)數(shù)顷窒。

3 其它優(yōu)化

3.1 避免使用CGO或者減少CGO調(diào)用次數(shù)

GO可以調(diào)用C庫函數(shù),但Go帶有垃圾收集器且Go的棧動(dòng)態(tài)增漲源哩,但這些無法與C無縫地對接鞋吉。Go的環(huán)境轉(zhuǎn)入C代碼執(zhí)行前,必須為C創(chuàng)建一個(gè)新的調(diào)用棧励烦,把棧變量賦值給C調(diào)用棧谓着,調(diào)用結(jié)束現(xiàn)拷貝回來。而這個(gè)調(diào)用開銷也非常大坛掠,需要維護(hù)Go與C的調(diào)用上下文赊锚,兩者調(diào)用棧的映射。相比直接的GO調(diào)用棧屉栓,單純的調(diào)用椣掀眩可能有2個(gè)甚至3個(gè)數(shù)量級以上。

建議:盡量避免使用CGO友多,無法避免時(shí)牲平,要減少跨CGO的調(diào)用次數(shù)。

3.2 減少[]byte與string之間轉(zhuǎn)換域滥,盡量采用[]byte來字符串處理

GO里面的string類型是一個(gè)不可變類型欠拾,不像c++中std:string,可以直接char*取值轉(zhuǎn)化骗绕,指向同一地址內(nèi)容藐窄;而GO中[]byte與string底層兩個(gè)不同的結(jié)構(gòu),他們之間的轉(zhuǎn)換存在實(shí)實(shí)在在的值對象拷貝酬土,所以盡量減少這種不必要的轉(zhuǎn)化

建議:存在字符串拼接等處理荆忍,盡量采用[]byte,例如:

func Prefix(b []byte) []byte {

return append([]byte("hello", b...))

}

3.3 字符串的拼接優(yōu)先考慮bytes.Buffer

由于string類型是一個(gè)不可變類型撤缴,但拼接會創(chuàng)建新的string刹枉。GO中字符串拼接常見有如下幾種方式:

string + 操作 :導(dǎo)致多次對象的分配與值拷貝

fmt.Sprintf :會動(dòng)態(tài)解析參數(shù),效率好不哪去

strings.Join :內(nèi)部是[]byte的append

bytes.Buffer :可以預(yù)先分配大小屈呕,減少對象分配與拷貝

建議:對于高性能要求微宝,優(yōu)先考慮bytes.Buffer,預(yù)先分配大小虎眨。非關(guān)鍵路徑蟋软,視簡潔使用镶摘。fmt.Sprintf可以簡化不同類型轉(zhuǎn)換與拼接。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末岳守,一起剝皮案震驚了整個(gè)濱河市凄敢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌湿痢,老刑警劉巖涝缝,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異譬重,居然都是意外死亡拒逮,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門臀规,熙熙樓的掌柜王于貴愁眉苦臉地迎上來滩援,“玉大人,你說我怎么就攤上這事以现『菰梗” “怎么了约啊?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵邑遏,是天一觀的道長。 經(jīng)常有香客問我恰矩,道長记盒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任外傅,我火速辦了婚禮纪吮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘萎胰。我一直安慰自己碾盟,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布技竟。 她就那樣靜靜地躺著冰肴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪榔组。 梳的紋絲不亂的頭發(fā)上熙尉,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機(jī)與錄音搓扯,去河邊找鬼检痰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛锨推,可吹牛的內(nèi)容都是我干的铅歼。 我是一名探鬼主播公壤,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼谭贪!你這毒婦竟也來了境钟?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤俭识,失蹤者是張志新(化名)和其女友劉穎慨削,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體套媚,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缚态,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了堤瘤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片玫芦。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖本辐,靈堂內(nèi)的尸體忽然破棺而出桥帆,到底是詐尸還是另有隱情,我是刑警寧澤慎皱,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布老虫,位于F島的核電站,受9級特大地震影響茫多,放射性物質(zhì)發(fā)生泄漏祈匙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一天揖、第九天 我趴在偏房一處隱蔽的房頂上張望夺欲。 院中可真熱鬧,春花似錦今膊、人聲如沸些阅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽市埋。三九已至,卻和暖如春赖钞,著一層夾襖步出監(jiān)牢的瞬間腰素,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工雪营, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弓千,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓献起,卻偏偏與公主長得像洋访,于是被迫代替她去往敵國和親镣陕。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容