我们现在可以轻松地遵循粗箭头,看看 FindLoops
是否触发了大部分垃圾收集。 如果我们列出 FindLoops
们可以看到,大部分在开始时是正确的:
(pprof) list FindLoops ... . . 270: func FindLoops(cfgraph *CFG, lsgraph *LSG) { . . 271: if cfgraph.Start == nil { . . 272: return . . 273: } . . 274: . . 275: size := cfgraph.NumNodes() . . 276: . 145 277: nonBackPreds := make([][]int, size) . 9 278: backPreds := make([][]int, size) . . 279: . 1 280: number := make([]int, size) . 17 281: header := make([]int, size, size) . . 282: types := make([]int, size, size) . . 283: last := make([]int, size, size) . . 284: nodes := make([]*UnionFindNode, size, size) . . 285: . . 286: for i := 0; i < size; i++ { 2 79 287: nodes[i] = new(UnionFindNode) . . 288: } ... (pprof)
每次调用 FindLoops
,它都会分配一些相当大的 bookkeeping structures。 由于benchmark调用 FindLoops
50次,因此这些增加了大量的垃圾,所以垃圾收集器的工作量很大。
使用垃圾收集语言并不意味着您可以忽略内存分配问题。 在这种情况下,一个简单的解决方案是引入一个缓存,以便每次调用 FindLoops
时尽可能重用前一个调用的存储。
事实上,在Hundt的论文中,他解释说Java程序只需要进行这种改变就可以得到合理的性能,但是他没有在其他垃圾收集的实现中做出相同的改变。
我们将添加一个全局cache结构:
var cache struct { size int nonBackPreds [][]int backPreds [][]int number []int header []int types []int last []int nodes []*UnionFindNode }
然后把它当作 FindLoops
内存分配的替代品:
if cache.size < size { cache.size = size cache.nonBackPreds = make([][]int, size) cache.backPreds = make([][]int, size) cache.number = make([]int, size) cache.header = make([]int, size) cache.types = make([]int, size) cache.last = make([]int, size) cache.nodes = make([]*UnionFindNode, size) for i := range cache.nodes { cache.nodes[i] = new(UnionFindNode) } } nonBackPreds := cache.nonBackPreds[:size] for i := range nonBackPreds { nonBackPreds[i] = nonBackPreds[i][:0] } backPreds := cache.backPreds[:size] for i := range nonBackPreds { backPreds[i] = backPreds[i][:0] } number := cache.number[:size] header := cache.header[:size] types := cache.types[:size] last := cache.last[:size] nodes := cache.nodes[:size]
当然,这样的全局变量是糟糕的工程实践:它意味着对 FindLoops
并发调用现在是不安全的。 目前,我们正在进行尽可能少的更改,以便了解对我们的程序的性能有什么重要意义; 这种变化很简单,并且反映了Java实现中的代码。 Go程序的最终版本将使用单独的 LoopFinder
实例来跟踪此内存,从而恢复并发使用的可能性。
$ make havlak5 go build havlak5.go $ ./xtime ./havlak5 # of loops: 76000 (including 1 artificial root node) 8.03u 0.06s 8.11r 770352kB ./havlak5 $
总结
我们可以做更多的事情来清理程序并使其更快,但是它们都不需要我们尚未展示的分析技术。 内部循环中使用的工作列表可以跨迭代和跨调用进行重用。 FindLoops,它可以与在该过程中生成的单独的“节点池”相结合。 类似地,循环图存储可以在每次迭代时重用,而不是重新分配。 除了这些性能变化之外, 最终版本是使用惯用的Go样式编写的,使用数据结构和方法。 风格变化对运行时间的影响很小:算法和约束不变。
最终版本运行2.29秒,使用351 MB内存:
$ make havlak6 go build havlak6.go $ ./xtime ./havlak6 # of loops: 76000 (including 1 artificial root node) 2.26u 0.02s 2.29r 360224kB ./havlak6 $
这比我们开始的程序快11倍。 即使我们禁用对生成的循环图的重用,以便唯一的缓存内存是循环查找bookeeping,程序仍然比原始运行速度快6.7倍,并且使用的内存减少1.5倍。
$ ./xtime ./havlak6 -reuseloopgraph=false # of loops: 76000 (including 1 artificial root node) 3.69u 0.06s 3.76r 797120kB ./havlak6 -reuseloopgraph=false $
当然,将这个Go程序与原始的C++程序进行比较是不公平的,因为它使用了低效的数据结构,例如 set
s 其中 vector
s 更合适。 作为完整性检查,我们将最终的Go程序翻译成等效的C++代码。 它的执行时间类似于Go程序:
$ make havlak6cc g++ -O3 -o havlak6cc havlak6.cc $ ./xtime ./havlak6cc # of loops: 76000 (including 1 artificial root node) 1.99u 0.19s 2.19r 387936kB ./havlak6cc
Go程序的运行速度几乎和C++程序一样快。 由于C++程序使用自动删除和分配而不是显式缓存,因此C++程序更短更容易编写,但不是那么明显:
$ wc havlak6.cc; wc havlak6.go 401 1220 9040 havlak6.cc 461 1441 9467 havlak6.go $
见 havlak6.cc 和 havlak6.go
Benchmarks与他们测量的程序一样好。 我们使用 go tool pprof
来研究低效的Go程序,然后将其性能提高一个数量级,并将其内存使用量减少3.7倍。 随后与等效优化的C++程序进行比较表明,当程序员小心内循环生成多少垃圾时,Go可以与C++竞争。
用于编写这篇文章的程序源代码,Linux x86-64二进制文件和配置文件可以在GitHub上的benchgraffiti项目中找到 。
如上所述, go test
已经包含了这些 profiling flags:benchmark function ,你就完成了。 还有一个用于 profiling 数据的标准HTTP接口。 在HTTP服务器中,添加
import _ "net/http/pprof"
将安装几个URL的处理程序 /debug/pprof/
. 然后,您可以用一个参数运行 go tool pprof
——指向服务器profiling数据的URL,它将下载并检查实时Profile文件。
go tool pprof http://localhost:6060/debug/pprof/profile # 30-second CPU profile go tool pprof http://localhost:6060/debug/pprof/heap # heap profile go tool pprof http://localhost:6060/debug/pprof/block # goroutine blocking profile
goroutine blocking profile 将在以后的文章中解释。 敬请关注。
作者:Russ Cox,2011年7月; 由Shenghou Ma更新,2013年5月
原文:https://blog.golang.org/profiling-go-programs
源代码:https://github.com/cyningsun/go-test
本文作者 : cyningsun
本文地址 : https://www.cyningsun.com/07-20-2019/profiling-go-programs-cn.html
版权声明 :本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!