49 | 程序性能分析基础(下)
在上一篇文章中,我们围绕着“怎样让程序对 CPU 概要信息进行采样”这一问题进行了探讨,今天,我们再来一起看看它的拓展问题。
知识扩展
问题 1:怎样设定内存概要信息的采样频率?
针对内存概要信息的采样会按照一定比例收集 Go 程序在运行期间的堆内存使用情况。设定内存概要信息采样频率的方法很简单,只要为runtime.MemProfileRate变量赋值即可。
这个变量的含义是,平均每分配多少个字节,就对堆内存的使用情况进行一次采样。如果把该变量的值设为0,那么,Go 语言运行时系统就会完全停止对内存概要信息的采样。该变量的缺省值是512 KB,也就是512千字节。
注意,如果你要设定这个采样频率,那么越早设定越好,并且只应该设定一次,否则就可能会对 Go 语言运行时系统的采样工作,造成不良影响。比如,只在main函数的开始处设定一次。
在这之后,当我们想获取内存概要信息的时候,还需要调用runtime/pprof包中的WriteHeapProfile函数。该函数会把收集好的内存概要信息,写到我们指定的写入器中。
注意,我们通过WriteHeapProfile函数得到的内存概要信息并不是实时的,它是一个快照,是在最近一次的内存垃圾收集工作完成时产生的。如果你想要实时的信息,那么可以调用runtime.ReadMemStats函数。不过要特别注意,该函数会引起 Go 语言调度器的短暂停顿。
以上,就是关于内存概要信息的采样频率设定问题的简要回答。
package main
import (
"048程序性能分析基础/common"
"048程序性能分析基础/common/op"
"errors"
"fmt"
"os"
"runtime"
"runtime/pprof"
)
var (
profileName = "memprofile.out"
memProfileRate = 8
)
func main() {
f, err := common.CreateFile("", profileName)
if err != nil {
fmt.Printf("memory profile creation error: %v\n", err)
return
}
defer f.Close()
startMemProfile()
if err = common.Execute(op.MemProfile, 10); err != nil {
fmt.Printf("execute error: %v\n", err)
return
}
if err := stopMemProfile(f); err != nil {
fmt.Printf("memory profile stop error: %v\n", err)
return
}
}
func startMemProfile() {
runtime.MemProfileRate = memProfileRate
}
func stopMemProfile(f *os.File) error {
if f == nil {
return errors.New("nil file")
}
return pprof.WriteHeapProfile(f)
}
问题 2:怎样获取到阻塞概要信息?
我们调用runtime包中的SetBlockProfileRate函数,即可对阻塞概要信息的采样频率进行设定。该函数有一个名叫rate的参数,它是int类型的。
这个参数的含义是,只要发现一个阻塞事件的持续时间达到了多少个纳秒,就可以对其进行采样。如果这个参数的值小于或等于0,那么就意味着 Go 语言运行时系统将会完全停止对阻塞概要信息的采样。
在runtime包中,还有一个名叫blockprofilerate的包级私有变量,它是uint64类型的。这个变量的含义是,只要发现一个阻塞事件的持续时间跨越了多少个 CPU 时钟周期,就可以对其进行采样。它的含义与我们刚刚提到的rate参数的含义非常相似,不是吗?
实际上,这两者的区别仅仅在于单位不同。runtime.SetBlockProfileRate函数会先对参数rate的值进行单位换算和必要的类型转换,然后,它会把换算结果用原子操作赋给blockprofilerate变量。由于此变量的缺省值是0,所以 Go 语言运行时系统在默认情况下并不会记录任何在程序中发生的阻塞事件。
另一方面,当我们需要获取阻塞概要信息的时候,需要先调用runtime/pprof包中的Lookup函数并传入参数值"block",从而得到一个*runtime/pprof.Profile类型的值(以下简称Profile值)。在这之后,我们还需要调用这个Profile值的WriteTo方法,以