標籤(空格分隔): Go Memory Profiler 性能調試 性能分析golang
注:該文做者是 Dmitry Vyukov,原文地址 Debugging performance issues in Go programs數組
這個是原文中的 Memory Profiler 段落瀏覽器
內存分析器顯示了函數分配堆內存的狀況。你能夠以 CPU profile 類似的方式收集:使用 go test --memprofile,經過 http://myserver:6060:/debug/pprof/heap 使用 net/http/pprof 或是經過調用 runtime/pprof.WriteHeapProfile。函數
你僅能夠顯示在概要文件收集的時間分配的內存(默認,pprof 的 --inuse_space 標誌),或是從程序啓動起的全部分配(pprof 的 --alloc_space 標誌)。前者對對於 net/http/pprof 的現場應用的概要文件收集很是有用,後者對程序結束的時候的概要文件收集很是有用(不然你將看到空蕩蕩的概要文件)。oop
注意:內存分析器很簡單,也就是說,它收集的信息僅僅是關於內存分配的一些子集。機率抽樣對象與它的大小成正比,你可使用
go test --memprofilerate
標誌改變抽樣比率,或者是在程序啓動的時候設置runtime.MemProfileRate
變量。比率 1 將致使收集全部分配的信息。可是它可能致使執行很慢,默認的採樣率是每 512kb 的內存分配 1個樣本。性能
你也能夠顯示分配的字節數,或者是分配的對象數量(--inuse/alloc_space
和 --inuse/alloc_objects
標誌)。分析器在分析時更傾向於大樣本對象。可是更重要的是要明白大對象影響內存消耗和 GC 時間,然而大量微小的分配影響執行速度(一樣是某種程度的 GC 時間),因此兩個都觀察多是很是有用的。優化
對象能夠是持久的或是瞬態的。若是在程序開始的時候,你有一些大的持久化對象分配,它們將最有可能被分析器採樣(由於它們足夠大)。這樣的對象會影響內存的消耗和 GC 時間,可是它們不影響正常的執行速度(沒有內存管理操做發生在它們身上)。換句話說,若是你有大量的生命週期很是短暫的對象,在概要文件中,它們幾乎能夠表明(若是你使用默認的 --inuse_space 模式),但它們很明顯的會影響執行速度。由於它們在不斷的分配和釋放。所以,再一次聲明,觀察兩種類型的對象是很是有用的。ui
所以,一般若是你想下降內存消耗,在正常的程序操做期間,你須要查看 --inuse_space
概要文件收集。若是你想提高執行速度,查看 --alloc_objects
概要文件收集,在重要的運行時間或程序結束以後。spa
這有一些標誌控制報告的粒度。--functions
使得 pprof
報告在函數級別(默認)。--lines
使得 pprof
報告在源碼的行級別。這是很是有用的,若是熱函數在不一樣的行。這裏也有 --addresses
和 --files
各自對應準確的指令地址和文件級別。debug
對於內存概要文件來講,這是很是有用的選項 -- 你能夠在瀏覽器中查看它(提供這個功能須要你 imported net/http/pprof)。若是你打開 http://myserver:6060/debug/pprof/heap?debug=1,你必須看到堆相似:
heap profile: 4: 266528 [123: 11284472] @ heap/1048576 1: 262144 [4: 376832] @ 0x28d9f 0x2a201 0x2a28a 0x2624d 0x26188 0x94ca3 0x94a0b 0x17add6 0x17ae9f 0x1069d3 0xfe911 0xf0a3e 0xf0d22 0x21a70 # 0x2a201 cnew+0xc1 runtime/malloc.goc:718 # 0x2a28a runtime.cnewarray+0x3a runtime/malloc.goc:731 # 0x2624d makeslice1+0x4d runtime/slice.c:57 # 0x26188 runtime.makeslice+0x98 runtime/slice.c:38 # 0x94ca3 bytes.makeSlice+0x63 bytes/buffer.go:191 # 0x94a0b bytes.(*Buffer).ReadFrom+0xcb bytes/buffer.go:163 # 0x17add6 io/ioutil.readAll+0x156 io/ioutil/ioutil.go:32 # 0x17ae9f io/ioutil.ReadAll+0x3f io/ioutil/ioutil.go:41 # 0x1069d3 godoc/vfs.ReadFile+0x133 godoc/vfs/vfs.go:44 # 0xfe911 godoc.func·023+0x471 godoc/meta.go:80 # 0xf0a3e godoc.(*Corpus).updateMetadata+0x9e godoc/meta.go:101 # 0xf0d22 godoc.(*Corpus).refreshMetadataLoop+0x42 godoc/meta.go:141 2: 4096 [2: 4096] @ 0x28d9f 0x29059 0x1d252 0x1d450 0x106993 0xf1225 0xe1489 0xfbcad 0x21a70 # 0x1d252 newdefer+0x112 runtime/panic.c:49 # 0x1d450 runtime.deferproc+0x10 runtime/panic.c:132 # 0x106993 godoc/vfs.ReadFile+0xf3 godoc/vfs/vfs.go:43 # 0xf1225 godoc.(*Corpus).parseFile+0x75 godoc/parser.go:20 # 0xe1489 godoc.(*treeBuilder).newDirTree+0x8e9 godoc/dirtrees.go:108 # 0xfbcad godoc.func·002+0x15d godoc/dirtrees.go:100
在每一個入口開始的數字 ("1: 262144 [4: 376832]") 表明當前存活對象的數量,存活對象已經佔用的內存,分配的總的數量和全部分配已經佔用的內存。
優化一般特定於應用程序,但這裏有一些常見的建議。
局部變量逃離了它們聲明的範圍,提高到堆分配。編譯器一般不能證實幾個變量有相同的壽命,所以它分別分配每一個這樣的變量。所以你可使用以上的建議處理局部變量,好比,把下面這個:
for k, v := range m { k, v := k, v // copy for capturing by the goroutine go func() { // use k and v }() }
替代爲:
for k, v := range m { x := struct{ k, v string }{k, v} // copy for capturing by the goroutine go func() { // use x.k and x.v }() }
這會把兩個內存分配變爲一個內存分配。儘管如此,該優化會影響代碼的可讀性,因此請合理使用它。
分配的一個特例就是 slice 數組預分配。若是你知道一個 slice 的標準大小,你能夠像下面這樣預分配一個支持數組:
type X struct { buf []byte bufArray [16]byte // Buf usually does not grow beyond 16 bytes. } func MakeX() *X { x := &X{} // Preinitialize buf with the backing array. x.buf = x.bufArray[:0] return x }
你可使用垃圾收集器跟蹤(見下文)來獲得一些內存問題更深入的看法。