这篇文章部分转自:https://www.cnblogs.com/qcrao-2018/p/11832732.html
目录
1,CPU 优化
2,内存优化
3,GC 优化
4,小结
这篇文章讲了一个故事,大意是 Go 语言大佬 Russ Cox 发现一篇文章说使用 Go 写的算法很慢,而 C++ 很快。大佬哪受得了这个,于是使用 Go 语言性能优化神器 pprof,开始了优化实战。最终经过三轮优化,基本上能达到和 C++ 同等的速度和同样的内存消耗。这三轮优化十分具有借签与学习之意义,简述如下:
1,CPU 优化
首先,使用如下代码开启 CPU 监测:
pprof.StartCPUProfile(f)
耗时 top5 的函数,发现一个读 map 的函数耗时最长:mapaccess1_fast64,而它出现在一个递归函数中。执行 web mapaccess1 命令,聚焦出调用 mapaccess1_fast64 函数最多的就是 main.FindLoops 和 main.DFS,再执行命令:list DFS,定位到相关的具体代码。
优化的方法是将 map 改成 slice。当然并不是所有的map都需要这样做,在这个案例中能这样做,和 key 的类型是 int 而且不是太稀疏有关。具体问题需要具体分析,这正是 pprof 工具的价值所在。
修改完之后,再次通过 cpu profiling,发现递归函数 mapaccess1_fast64 的耗时已经不在 top5 中了,但是新增了长耗时函数:runtime.mallocgc,占比 54.2%,而这和内存分配以及垃圾回收相关。
2,内存优化
使用如下代码开启内存监测:
FindHavlakLoops(cfgraph, lsgraph)
f,_ := os.Create(*memprofile)
pprof.WriteHeapProfile(f)
继续通过 top5、list 命令找到内存分配最多的代码位置,发现这回是向 map 里插入元素使用的内存比较多。
改进方式同样是用 slice 代替 map。但 map 有一个缺点,就是可以重复插入元素,因此写一个向 slice 插入元素的函数,可以防止重复插入:
func appendUnique(a []int, x int) []int {
for _, y := range a {
if x == y {
return a
}
}
return append(a, x)
}
好了,现在程序比最初的时候快了 2.1 倍。再次查看 cpu profile 数据,发现 runtime.mallocgc 降了一些,但仍然占比 50.9%。因此需要查看垃圾回收到底在回收哪些内容,这些内容就是导致频繁垃圾回收的“罪魁祸首”。
3,GC 优化
使用 web mallocgc 命令,将和 mallocgc 相关的函数用矢量图的方式展现出来,但是有太多样本量很少的节点影响观察,增加过滤命令:
go tool pprof --nodefraction=0.1 profile
在这条指令中,将少于 10% 的采样点过滤掉。
(注:这是 pprof 生成的分析结果)
新的矢量图可以直观地看出,FindLoops 触发了最多的垃圾回收操作。继续使用命令 list FindLoops 直接找到代码的位置。原来,每次执行 FindLoops 函数时,都要 make 一些临时变量,这会加重垃圾回收器的负担。
改进方式是增加一个全局变量 cache,这样可以重复利用。虽然这样做的坏处是,线程不是安全的了。
4,小结
使用 pprof 工具进行的优化到这就结束了。最后值得提一下,大佬做这件事的时间比较久远,最初写于 2011 年,原文在这里:
https://blog.golang.org/profiling-go-programs
(Russ Cox 优化过程,并附上代码)
原文有更多内容和参考资料,如果有时间推荐查看。
pprof 的包路径是 "runtime/pprof",可以通用报告生成、Web 可视化界面、交互式终端 三种方式来使用。具体使用方法可查相关文档。