标签(空格分隔): 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 }
你可使用垃圾收集器跟踪(见下文)来获得一些内存问题更深入的看法。