從Baa開發中總結Go語言效能漸進最佳化

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

在Go生態已經有很多WEB架構,但感覺沒有一個符合我們的想法,我們想要一個簡潔高效的核心架構,提供路由context中介軟體依賴注入,而且拒絕使用正則反射,於是我們開始構建Baa架構。一開始使用最簡單的通俗寫法實現了第一版的功能,基本可用,但是效能爛到爆,最佳化之路漫漫開啟。

最好的文章應該是每一步都加上最佳化前後的benchmark對比結果,給讀者以最直觀的感受。我先BS一下自己,因為我懶了,沒有再回頭一步步去對比這個結果圖。

拒絕正則和反射

這是我們做這個架構時的一個基本原則,整個實現中沒有使用過regexp、reflect包。這是我們對效能追求的基礎。帶來的另一個收益是,沒有魔法,都是非常容易理解的實現,讓整個架構變得簡單。

使用sync.Pool重用對象

在我上次翻譯的文章CockroachDB GC最佳化總結中介紹過這些方法,在《Go語言聖經》中作者也介紹了這個方法,使用 sync.Pool 可以在一次GC之間重用對象,避免對象的頻繁建立和記憶體配置。我們在追求效能的過程中,要儘可能減少甚至達到記憶體零分配,這是一個最重要的用法。

在Baa中有如下程式碼片段:

b.pool = sync.Pool{    New: func() interface{} {        return newContext(nil, nil, b)    },}

使用的時候:

c := b.pool.Get().(*Context)c.reset(w, r)

使用完:

b.pool.Put(c)

使用array最佳化slice

slice的本質就是就是一個可變長度的array,根據儲存的容量會動態重新分配記憶體遷移資料。如果長度不斷變化,會導致不斷的重新分配記憶體,在特定情境下,如果我們可以使用一個定長的array來最佳化記憶體配置。

var nameArr [1024]stringpNames := nameArr[0:0]pNames = append(pNames, "val")

pNames 是一個slice,但資料操作總是在array nameArr上完成,在整個使用過程中不會重新分配記憶體。

上面的虛擬碼,在Baa中已經不存在了,Baa改用了下面的技巧來取代定長的array。

slice也能重用

slice的重用,其實和上面的利用array最佳化基本一致,就是初始分配一個較大的容量,儘可能在使用的過程中都不會超出容量,當然也不用擔心,萬一不夠用了,會自動擴容,只不過會進行一次記憶體配置。

在Baa中有如下程式碼片段:

// newContext create a http contextfunc newContext(w http.ResponseWriter, r *http.Request, b *Baa) *Context {    c := new(Context)    c.Resp = NewResponse(w, b)    c.baa = b    c.pNames = make([]string, 0, 32)    c.pValues = make([]string, 0, 32)    c.handlers = make([]HandlerFunc, len(b.middleware), len(b.middleware)+3)    copy(c.handlers, b.middleware)    c.reset(w, r)    return c}// reset ...func (c *Context) reset(w http.ResponseWriter, r *http.Request) {    c.Resp.reset(w)    c.Req = r    c.hi = 0    c.handlers = c.handlers[:len(c.baa.middleware)]    c.pNames = c.pNames[:0]    c.pValues = c.pValues[:0]    c.store = nil}

注意newContext中的 c.pNames和c.pValues 以及 reset中的 c.pNames和c.pValues,通過 slice[:0] 來重用之前的slice,避免記憶體重新分配。至於上面的長度32,是根據經驗得來的一個值,儘可能保證長度滿足大部分情況下的需求又不太大。

使用Radix tree重寫路由

之前在黑夜路人群中還討論過一個問題:演算法、資料結構,在實際工作中有用到過嗎?說實話,一般情況下真不怎麼用到,不過這裡就是一個情境。

在第一版中,路由就是一個map,路由匹配就是一個range,簡單,清晰,但效能自然不好。參考了 macaronecho架構的設計,都是使用基數樹(radix tree)來實現的,只是實現的細節不同,這裡我們也有不同的細節實現,但思路基本沒變。具體實現可以參考 wiki,和 Baa router部分 router.go

string的效能不怎樣

很多文章介紹過了,盡量使用 []byte 替代 string,這裡我們也是這麼做的。

Map的range好低效

map和slice的range效能差一個數量級啊,所以,你會發現我們取消了大量的map改為了slice,在slice也能重用這一節的程式碼範例中 pNames和pValues就是用來取代原來的 map[string]string,因為map range的效率太低了。

凡是迭代就有開銷

slice的迭代是很快,可是總還是迭代,是迭代就有開銷,為了追求極致的效能也是瘋了。在路由匹配時,我們給所有的路由pattern設定了單位元組的index,如果首字母都不匹配,就沒有必要繼續後面的字元匹配了。

路由條目建立:

// newRoute create a route itemfunc newRoute(pattern string, handles []HandlerFunc, router *Router) *Route {    r := new(Route)    r.pattern = pattern    r.alpha = pattern[0]    r.handlers = handles    r.router = router    r.children = make([]*Route, 0)    return r}

路由條目匹配:

// findChild find child static routefunc (r *Route) findChild(b byte) *Route {    var i int    var l = len(r.children)    for ; i < l; i++ {        if r.children[i].alpha == b && !r.children[i].hasParam {            return r.children[i]        }    }    return nil}

注意 r.alpha 就是用來儘可能避免迭代進一步提高效能的。

defer也僅是方便

在追求極致效能的路上,我都快瘋了,在一步步測試的過程中,發現去掉defer也能提高一些效能,雨痕學堂公眾號 中的一篇文章也提到了這個問題,因為defer有額外的開銷來保證延遲調用甚至panic時也能執行,而大多數時候我們可以在程式的結束時直接終止,避免defer機制,再快一點點。

函數調用也是開銷

離目標越來越近,但還有一點差距,我們也越來越瘋狂,最後居然幹成了這樣,我們把部分頻繁調用的函數取消,改為直接在一個函數中完成,因為我們發現,即使只是一個函數調用,TMD也是開銷呀。

pprof是神器

在整個過程中,如何一步步分析效能問題,定位可最佳化的地方,go test -cpuprofile, go test -memprofile, go test -bench 就是最好的工具,每修改一次,bench看結果,profile看效能分析。

總結

本文簡單總結了在最佳化過程中的各種技巧,和部分程式碼範例,更多使用姿勢,自行體驗,歡迎交流和拍磚。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.