Golang FlameGraph(火焰圖)

簡介

初學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

pprof內存分配圖效果

輸入圖片說明

從上到下是調用關係,如箭頭所示,表示給每一個函數【累計】分配了多少內存,包括它本身佔用多少以及向下調用時分配了多少。從這個就能夠看出程序中哪一個地方最消耗內存,最底下沒有名字的方塊是這個函數內,每次向系統申請內存的大小數組

實際圖片是svg格式的,能夠無限方法,這裏只是看個大概(人爲打碼)。瀏覽器

調優實踐

先說一下結論吧,性能限制主要是IO相關的,好比網絡數據收發、磁盤讀寫等,在程序複雜度並無那麼高的狀況下,調優只是錦上添花,主要能夠幫助本身更好的瞭解這個語言。 如下調優的部分主要是針對項目中,從github上引入的部分代碼bash

CPU使用調優

結果圖

先來看看先後的對比圖:網絡

輸入圖片說明

調優前,兩個藍色方框中的函數分別是StringDefaultRead,前者的做用是把二進制表示的數值轉爲對應大小的字符串([]byte -> int -> string),後者是將二進制讀到指定的位置,這兩個函數佔用了40%+的時間。優化後以下:app

輸入圖片說明

因爲對前面兩個部分的優化,佔用的時間已經大大縮小,從70%左右降低到40%+。後面沒有作處理的網絡讀寫net.Write佔了不少時間svg

調優過程

首先針對字符串處理部分,從第一個火焰圖點擊StringDefault能夠看到細節,以下圖:

輸入圖片說明

裏面有不少的readnewobject,從函數功能上咱們能夠知道,轉換一次字符串並不須要這麼麻煩,來看看代碼是這麼寫的:

// 原來的寫法,以雙字節爲例
// 先申請臨時變量,將字節數組轉爲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圖,觀察流程是否合理,是否能夠簡化,以及每一個函數的內存分配狀況,具體過程不像上面那麼清洗,都是小修小補,故直接總結一些可能不夠可靠的經驗:

  1. 減小沒必要要的臨時變量,函數的參數若是比較長則應該傳遞指針
  2. 在字節流處理中,原來常常出現使用 bytes.NewBuffer(buffer) 做爲參數的狀況,這種用法是爲了使用 bytes.Buffer 的一系列函數,可是須要從新申請一次空間,其實這樣會多申請一個bytes.Buffer對象,若是操做比較簡單,能夠直接對buffer數組進行,不用轉換。還有就是 string 的轉換也會申請空間,好比把 []byte 轉 string ,作個簡單的處理又轉成 []byte 發送出去 ,能夠儘可能去掉中間的過程
  3. 若是已知切片大小,直接make出指定長度,不然頻繁的 grow 佔用資源
相關文章
相關標籤/搜索