概要
profile就是定時(shí)采樣抹剩,收集cpu撑帖,內(nèi)存等信息,進(jìn)而給出性能優(yōu)化指導(dǎo)澳眷,golang 官方提供了golang自己的性能分析工具的用法胡嘿,也給出了指導(dǎo),官方的介紹
環(huán)境
golang環(huán)境境蔼, graphviz
生成profile方法
golang目前提供了3中profile灶平,分別是 cpu profile, memery profile, blocking profile, 對(duì)于如何生成這些profile有兩種辦法伺通,一種是使用 net/http/pprof 包,一種是需要自己手寫代碼逢享,下面分別介紹一下
1. net/http/pprof 方法
這種方法是非常非常簡(jiǎn)單的罐监,只需要引入 net/http/pprof 包就可以了,網(wǎng)頁上可以查看
package main
import (
"fmt"
"log"
"net/http"
_ "net/http/pprof"
)
func main() {
go func() {
for {
fmt.Println("hello world")
}
}()
log.Fatal(http.ListenAndServe("0.0.0.0:8080", nil))
}
http://127.0.0.1:8080/debug/pprof 查看整體信息
http://127.0.0.1:8080/debug/pprof/profile 可以將cpu profile下載下來觀察分析
從terminal進(jìn)入profile瞒爬,進(jìn)行細(xì)致分析
go tool pprof http://localhost:6060/debug/pprof/profile
go tool pprof http://localhost:6060/debug/pprof/heap
go tool pprof http://localhost:6060/debug/pprof/block
- 寫代碼的方法
func main() {
cpuf, err := os.Create("cpu_profile")
if err != nil {
log.Fatal(err)
}
pprof.StartCPUProfile(cpuf)
defer pprof.StopCPUProfile()
ctx, _ := context.WithTimeout(context.Background(), time.Second*5)
test(ctx)
time.Sleep(time.Second * 3)
memf, err := os.Create("mem_profile")
if err != nil {
log.Fatal("could not create memory profile: ", err)
}
if err := pprof.WriteHeapProfile(memf); err != nil {
log.Fatal("could not write memory profile: ", err)
}
memf.Close()
}
func test(c context.Context) {
i := 0
j := 0
go func() {
m := map[int]int{}
for {
i++
m[i] = i
}
}()
go func() {
m := map[int]int{}
for {
j++
m[i] = i
}
}()
select {
case <-c.Done():
fmt.Println("done, i", i, "j", j)
return
}
}
會(huì)生成兩個(gè)profile弓柱,一個(gè)是cpu的,一個(gè)是內(nèi)存的侧但。進(jìn)入proflie 方法
go tool pprof main profile
main 代表的是二進(jìn)制文件矢空,也就是編譯出來的可執(zhí)行文件
profile 就是上文中生成的profile,可以是cpu_profile, 也可以是mem_profile
對(duì)于cpu_profile 來說禀横,代碼開始的時(shí)候就可以開始統(tǒng)計(jì)了
mem_profile 部分代碼如果寫在代碼開始的位置是統(tǒng)計(jì)不出來的屁药,需要找到一個(gè)比較好的位置
如何分析 profile
1.示例代碼如下
package main
import (
"flag"
"fmt"
"log"
"math/rand"
"os"
"runtime/pprof"
"sort"
"time"
)
var num = 100000000
var findNum = 10000000
var t = flag.String("t", "map", "use map")
func main() {
cpuf, err := os.Create("cpu_profile")
if err != nil {
log.Fatal(err)
}
pprof.StartCPUProfile(cpuf)
defer pprof.StopCPUProfile()
flag.Parse()
if *t == "map" {
fmt.Println("map")
findMapMax()
} else {
fmt.Println("slice")
findSliceMax()
}
}
func findMapMax() {
m := map[int]int{}
for i := 0; i < num; i++ {
m[i] = i
}
for i := 0; i < findNum; i++ {
toFind := rand.Int31n(int32(num))
_ = m[int(toFind)]
}
}
func findSliceMax() {
m := make([]int, num)
for i := 0; i < num; i++ {
m[i] = i
}
for i := 0; i < findNum; i++ {
toFind := rand.Int31n(int32(num))
v := sort.SearchInts(m, int(toFind))
fmt.Println(v)
}
}
代碼很簡(jiǎn)單,主要是為了介紹如何分析profile柏锄,達(dá)到效果即可酿箭,不要在意細(xì)節(jié)。
這段代碼就是分別用 map, slice 兩種數(shù)據(jù)結(jié)構(gòu), 先生成 num 個(gè)元素趾娃,在從map, slice 中 隨機(jī)找到 findNum 個(gè)元素缭嫡, 選用map 還是 slice 可以通過 -t 來指定,本demo采用 非 net/http/pprof 方式
2.準(zhǔn)備工作
需要生成 profile 文件 和 二進(jìn)制文件
生成二進(jìn)制文件: go build main.go (執(zhí)行命令后會(huì)生成 main 二進(jìn)制文件)
生成 profile: ./main (不指定-t 抬闷,默認(rèn)使用map數(shù)據(jù)結(jié)構(gòu),會(huì)生成 cpu_profile, 這個(gè)文件就是我們要分析的profile)
3.分析profile
- 現(xiàn)在準(zhǔn)備工作做好了妇蛀,我們目前生成了 main 二進(jìn)制可執(zhí)行文件,cpu_profile 性能分析需要的profile, 接下來我們要正式進(jìn)入profile進(jìn)行分析了
- go tool pprof main cpu_profile 執(zhí)行這個(gè)命令就進(jìn)入了profile 文件了笤成,這時(shí)候我們已經(jīng)可以開始分析代碼了
- 輸入help评架,可以查看都支持哪些操作,有很多命令可以根據(jù)需要進(jìn)行選擇疹启,我們只介紹4個(gè)我自己比較喜歡用的命令 web, top, peek, list
*web----- 在profile輸入 web, 會(huì)生成網(wǎng)頁版的調(diào)用分析圖(需要安裝 graphviz)如下圖:
輸入web命令后古程,會(huì)自動(dòng)打開瀏覽器出現(xiàn)如下內(nèi)容:
這樣就可以看到每個(gè)步驟占用多少時(shí)間了蔼卡,可以對(duì)性能進(jìn)行大致的分析喊崖,但是很多時(shí)候可能出現(xiàn)的并不是我們關(guān)心的,比如這個(gè)demo中看到的都是不認(rèn)識(shí)的函數(shù)(其實(shí)都是map的runtime操作)
-
top----- 在profile 中輸入top雇逞,會(huì)列出來幾個(gè)最耗時(shí)的操作
因?yàn)樾阅芙y(tǒng)計(jì)都是采樣操作荤懂,所以不是每次統(tǒng)計(jì)出來的都一樣, 最重要的是經(jīng)常統(tǒng)計(jì)出來的都是底層操作塘砸,并不是我們關(guān)心的节仿,而且也不是每個(gè)人都能看得懂,我們更需要一種直觀的辦法掉蔬,很直觀的能把自己寫的代碼耗時(shí)都看出來廊宪,下面就介紹一種我個(gè)人覺得非常好的方法矾瘾。
-
peek,list 妙用
peek 是用來查詢 函數(shù)名字的(這個(gè)名字是list需要使用的名字,并不完全等于函數(shù)名)
list 是用來將函數(shù)時(shí)間消耗列出來的
1)list main.main
-
peek findMapMax (因?yàn)楦鶕?jù)1可以看出來消耗都在 findMapMax)
3)list main.findMapMax (根據(jù)2可以看出來名字是 main.findMapMax)
妙用 peek list指令可以很直觀的看出來箭启,我們的代碼問題在 m[i] = i, 這就說明了就是map的寫操作耗費(fèi)了38.75s, 而44行的讀操作只用了2.35s, 針對(duì)這個(gè)demo壕翩,我們要優(yōu)化的就是 m[i] = i ,因?yàn)檫@句操作已經(jīng)語言級(jí)別的傅寡,我們是沒有能力對(duì)他進(jìn)行優(yōu)化的放妈,所以這時(shí)候如果需求只能用map,那么這個(gè)程序幾乎沒有優(yōu)化空間了荐操,如果需求可以使用其他的數(shù)據(jù)結(jié)構(gòu)芜抒,那我們肯定會(huì)把map修改為slice,眾所周知 map 每次存一個(gè)函數(shù)都要進(jìn)行hash計(jì)算托启,而且存的多了到達(dá)一定的數(shù)量宅倒,還要重新對(duì)map進(jìn)行重新分配空間的操作,所以肯定是耗時(shí)的屯耸。
總結(jié)
- go run main.go -t=slice 會(huì)使用slice的數(shù)據(jù)結(jié)構(gòu)唉堪,同學(xué)們可以自行按照文章的方法進(jìn)行分析一下,檢驗(yàn)一下自己掌握的如何肩民。
- 在profile中執(zhí)行help命令唠亚,會(huì)列出所有的命令,有興趣可以去研究
- memory 也是同樣的分析方法持痰,demo 中 mem_profile 生成的位置可能需要調(diào)整灶搜,我沒有進(jìn)行驗(yàn)證,降低memory 使用也會(huì)大幅提升性能工窍。
- 還有一種 blocking profile 在手寫方式中沒有給出割卖,有興趣的可以自己google一下