初學golang(一個月多),以前主要用其餘語言,若有問題歡迎指出。html
go get github.com/uber/go-torch # 再安裝 brendangregg/FlameGraph export PATH=$PATH:/absolute/path/FlameGraph-master # 還須要安裝一個graphviz用來畫內存圖 yum install graphviz
import "net/http" import _ "net/http/pprof" func main() { // 主函數中添加 go func() { http.HandleFunc("/program/html", htmlHandler) // 用來查看自定義的內容 log.Println(http.ListenAndServe("0.0.0.0:8080", nil)) }() }
# 用 -u 分析CPU使用狀況 ./go-torch -u http://127.0.0.1:8080 # 用 -alloc_space 來分析內存的臨時分配狀況 ./go-torch -alloc_space http://127.0.0.1:8080/debug/pprof/heap --colors=mem # 用 -inuse_space 來分析程序常駐內存的佔用狀況; ./go-torch -inuse_space http://127.0.0.1:8080/debug/pprof/heap --colors=mem # 畫出內存分配圖 go tool pprof -alloc_space -cum -svg http://127.0.0.1:8080/debug/pprof/heap > heap.svg
使用瀏覽器查看svg文件,程序運行中,能夠登陸 http://127.0.0.1:10086/debug/pprof/
查看程序實時狀態git
在此基礎上,能夠經過配置handle
來實現自定義的內容查看,能夠添加Html格式的輸出,優化顯示效果github
func writeBuf(buffer *bytes.Buffer, format string, a ...interface{}) { (*buffer).WriteString(fmt.Sprintf(format, a...)) } func htmlHandler(w http.ResponseWriter, req *http.Request) { io.WriteString(w, statusHtml()) } // 訪問 localhost:8080/program/html 能夠看到一個表格,一秒鐘刷新一次 func statusHtml() string { var buf bytes.Buffer buf.WriteString("<html><meta http-equiv=\"refresh\" content=\"1\">" + "<body><h2>netflow-decoder status count</h2>" + "<table width=\"500px\" border=\"1\" cellpadding=\"5\" cellspacing=\"1\">" + "<tr><th>NAME</th><th>TOTAL</th><th>SPEED</th></tr>") writeBuf(&buf, "<tr><td>UDP</td><td>%d</td><td>%d</td></tr>", total, speed) ... buf.WriteString("</table></body></html>") return buf.String() }
火焰圖自下而上是函數的調用關係,底下的一個方塊是入口,對應其上面的方塊是他直接或者間接調用到的,長度是運行時所佔用的CPU時長,顏色沒有特別的意義golang
從上到下是調用關係,如箭頭所示,表示給每一個函數【累計】分配了多少內存,包括它本身佔用多少以及向下調用時分配了多少。從這個就能夠看出程序中哪一個地方最消耗內存,最底下沒有名字的方塊是這個函數內,每次向系統申請內存的大小數組
實際圖片是svg格式的,能夠無限方法,這裏只是看個大概(人爲打碼)。瀏覽器
先說一下結論吧,性能限制主要是IO相關的,好比網絡數據收發、磁盤讀寫等,在程序複雜度並無那麼高的狀況下,調優只是錦上添花,主要能夠幫助本身更好的瞭解這個語言。 如下調優的部分主要是針對項目中,從github上引入的部分代碼bash
先來看看先後的對比圖:網絡
調優前,兩個藍色方框中的函數分別是StringDefault
和Read
,前者的做用是把二進制表示的數值轉爲對應大小的字符串([]byte -> int -> string),後者是將二進制讀到指定的位置,這兩個函數佔用了40%+的時間。優化後以下:app
因爲對前面兩個部分的優化,佔用的時間已經大大縮小,從70%左右降低到40%+。後面沒有作處理的網絡讀寫net.Write
佔了不少時間svg
首先針對字符串處理部分,從第一個火焰圖點擊StringDefault
能夠看到細節,以下圖:
裏面有不少的read
和newobject
,從函數功能上咱們能夠知道,轉換一次字符串並不須要這麼麻煩,來看看代碼是這麼寫的:
// 原來的寫法,以雙字節爲例 // 先申請臨時變量,將字節數組轉爲buffer(多餘且費時費內存),再讀取到臨時變量中,再進行類型轉換 var n uint16 binary.Read(bytes.NewBuffer(b), binary.BigEndian, &n) return strconv.Itoa(int(n)) // 精簡後:直接講字節數組轉爲對應長度的int,再轉爲字符串便可 return strconv.Itoa(int(binary.BigEndian.Uint16(b)))
同理,針對第一個火焰圖的Read
,定位到代碼以下:
// 逐字節讀取 binary.Read(每次新建一個臨時變量,並讀取一個字節,總共須要分紅n次讀取) // 目的是爲了將每一個直接按大端解析, n := recordSize for n > 0 { var field uint8 if err := binary.Read(buffer, binary.BigEndian, &field); err != nil { return 0, err } Fields = append(Fields, field) n -= 1 } // 然而實際上單字節內不論是網絡數據仍是內存中的數據都是同樣的,大小端主要是針對多字節的狀況,好比int類型的四個字節。 // 換成一次拷貝用 buffer.Next(n) 函數,直接把n個直接拷貝到對應位置 Fields = buffer.Next(recordSize)
這兩個簡單的修改就提高了不少性能。。因而可知,在從github上抄代碼時(¬_¬),特別是一些不知名的代碼仍是要本身審閱一遍。。
最終讓程序性能獲得重大提高的,是對最後net.Write
的優化。這個方法也很簡單,原來是每條消息發一次包,改爲拼接多條短消息,再發一個大包,大包的長度不要超過一個以太幀,本文使用UDP是不超過1450,預留了一點空間,反正也放不下一條消息。
內存調優主要是使用上面那個pprof圖,觀察流程是否合理,是否能夠簡化,以及每一個函數的內存分配狀況,具體過程不像上面那麼清洗,都是小修小補,故直接總結一些可能不夠可靠的經驗: