Open lzh2nix opened 4 years ago
原文: https://www.alexedwards.net/blog/an-overview-of-go-tooling
这不是简介这是十全大补丸
具体的check sheet https://github.com/fedir/go-tooling-cheat-sheet/blob/master/go-tooling-cheat-sheet.pdf
里面附带的几篇文章也是超级好: Profiling and optimizing Go web applications Debugging performance issues in Go programs Daily code optimization using benchmarks and profiling Profiling Go programs with pprof go tool trace
原文: https://blog.golang.org/pprof
官方出品而且是 Russ Cox 写的,havlak1cc.go 那个文件挺复杂的,没有太详细的去看,这里关注的是怎么使用go pprof找到性能瓶颈+优化思路。
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
func main() {
flag.Parse()
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
log.Fatal(err)
}
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
$ go tool pprof havlak1 havlak1.prof
(pprof) top10
Total: 2525 samples
298 11.8% 11.8% 345 13.7% runtime.mapaccess1_fast64
268 10.6% 22.4% 2124 84.1% main.FindLoops
251 9.9% 32.4% 451 17.9% scanblock
178 7.0% 39.4% 351 13.9% hash_insert
131 5.2% 44.6% 158 6.3% sweepspan
119 4.7% 49.3% 350 13.9% main.DFS
96 3.8% 53.1% 98 3.9% flushptrbuf
95 3.8% 56.9% 95 3.8% runtime.aeshash64
95 3.8% 60.6% 101 4.0% runtime.settype_flush
88 3.5% 64.1% 988 39.1% runtime.mallocgc
golang一秒采样100次左右
第一列/第二列表示在采样的出现的次数和占比, 比如 runtime.mallocgc 在整个2525次采样中出现了88次站比3.5%
第三列其实就是第二列的累加值,表示top N的占比
第四列/第五列表示该函数在采样中出现的次数, 比如runtime.mallocgc和988个sample相关,占比达到了39%
各个函数之间的调用关系可以通过图的形式查看:
查看代码
(pprof) list DFS
Total: 2525 samples
ROUTINE ====================== main.DFS in /home/rsc/g/benchgraffiti/havlak/havlak1.go
119 697 Total samples (flat / cumulative)
3 3 240: func DFS(currentNode *BasicBlock, nodes []*UnionFindNode, number map[*BasicBlock]int, last []int, current int) int {
1 1 241: nodes[current].Init(currentNode, current)
1 37 242: number[currentNode] = current
. . 243:
1 1 244: lastid := current
89 89 245: for _, target := range currentNode.OutEdges {
9 152 246: if number[target] == unvisited {
7 354 247: lastid = DFS(target, nodes, number, last, lastid+1)
. . 248: }
. . 249: }
7 59 250: last[number[currentNode]] = lastid
1 1 251: return lastid
(pprof)
var memprofile = flag.String("memprofile", "", "write memory profile to this file")
...
FindHavlakLoops(cfgraph, lsgraph)
if *memprofile != "" {
f, err := os.Create(*memprofile)
if err != nil {
log.Fatal(err)
}
pprof.WriteHeapProfile(f)
f.Close()
return
}
在生成图的时候drop掉占比低的节点
$ go tool pprof --nodefraction=0.1 havlak4 havlak4.prof
Welcome to pprof! For help, type 'help'.
(pprof) web mallocgc
在main.go 中 import _ "net/http/pprof"
然后直接:
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
整体的优化思路是: map--->slice-->cache 数据结构越简单越高效,尽量减少GC是优化之道
原文:https://blog.cloudflare.com/using-go-as-a-scripting-language-in-linux/
主要介绍了基于 gorun 的 golang 脚本运行方式。本文最大特点是介绍了一种通过 linux binfmt_misc 来直接脚本的方式
check binfmt_misc 已经挂载
$ mount | grep binfmt_misc
systemd-1 on /proc/sys/fs/binfmt_misc type autofs (rw,relatime,fd=27,pgrp=1,timeout=0,minproto=5,maxproto=5,direct)
将build好的 gorun 放到 /user/local/bin/下
.go 文件指定用gorun解析
$ echo ':golang:E::go::/usr/local/bin/gorun:OC' | sudo tee /proc/sys/fs/binfmt_misc/register
:golang:E::go::/usr/local/bin/gorun:OC
可以直接执行.go文件了
$ chmod u+x helloscript.go
$ ./helloscript.go
Hello, world!
原文: https://artem.krylysov.com/blog/2017/03/13/profiling-and-optimizing-go-web-applications/
使用工具基本和前面的一致, 对http server使用ab test进行测试然后使用go tool pprof xxx 进行性能分析。
指定 alloc_objects|inuse_objects 进行内存分配的分析
go tool pprof -alloc_objects http://127.0.0.1:8080/debug/pprof/heap
内存分配的benchmark:
go test -bench=. -benchmem
benchcmp 进行性能分析比对:
go test -run=NONE -bench=. ./... > old.txt
# make changes
go test -run=NONE -bench=. ./... > new.txt
然后:
benchcmp old.txt new.txt
func leftpad(s string, length int, char rune) string {
for len(s) < length {
s = string(char) + s
}
return s
}
vs
func leftpad(s string, length int, char rune) string { buf := bytes.Buffer{} for i := 0; i < length-len(s); i++ { buf.WriteRune(char) } buf.WriteString(s) return buf.String() }
- 尽量使用pass by pointer
- 如果知道map,slice的大小的话早点分配
- 避免log输出(每次输出都是一次io操作,很耗时)
- 顺序读写的场景使用bytes.Buffer{}
```golang
func (s *StatsD) Send(stat string, kind string, delta float64) {
buf := fmt.Sprintf("%s.", s.Namespace)
trimmedStat := strings.NewReplacer(":", "_", "|", "_", "@", "_").Replace(stat)
buf += fmt.Sprintf("%s:%s|%s", trimmedStat, delta, kind)
if s.SampleRate != 0 && s.SampleRate < 1 {
buf += fmt.Sprintf("|@%s", strconv.FormatFloat(s.SampleRate, 'f', -1, 64))
}
ioutil.Discard.Write([]byte(buf)) // TODO: Write to a socket
}
vs
func (s *StatsD) Send(stat string, kind string, delta float64) {
buf := bytes.Buffer{}
buf.WriteString(s.Namespace)
buf.WriteByte('.')
buf.WriteString(reservedReplacer.Replace(stat))
buf.WriteByte(':')
buf.Write(strconv.AppendFloat(make([]byte, 0, 24), delta, 'f', -1, 64))
buf.WriteByte('|')
buf.WriteString(kind)
if s.SampleRate != 0 && s.SampleRate < 1 {
buf.WriteString("|@")
buf.Write(strconv.AppendFloat(make([]byte, 0, 24), s.SampleRate, 'f', -1, 64))
}
buf.WriteTo(ioutil.Discard) // TODO: Write to a socket
}
chrome 打开 http://127.0.0.1:43591/trace 一片空白的解决方法: 关闭所有的chrome然后使用下面的参数打开
google-chrome --enable-blink-features=ShadowDOMV0,CustomElementsV0,HTMLImports
原文: https://golang.org/doc/diagnostics.html
官方一篇关于go tools的介绍性的文章,分别对Profile, Tracing,Debuging,Runtime statistics and events 进行了简单的介绍
这个可能是最常用的分析工具, 打开/debug/pprof 就可以看到如下的输出:
本来打算使用emitter来做一下这里的演示的,结果用了他的client发现一个bug, 然后就喜提一个PR https://github.com/emitter-io/go/pull/28.
一般通过 allocs/block/goroutine/mutex这几个的链接可以看出大概的的性能瓶颈在哪里了,然后再可以通过go tool pprof http://127.0.0.1:8080/debug/pprof/allocs 可以做进一步的分析,这里的话就比较直观一点。
也是第一次接触, 不过里面有几个特性确实不错,点击上面/debug/pprof的trace可以用生成trace文件,然后使用 go tool trace trace 进行加载,直接view trace的话貌似看不出山东西来,这里的goroutine分析比较有意思
这里可以看到所有goroutine的列表,感觉比pprof里的更直接一点,再点击一层之后可以看到:
从这里可以看到 在 github.com/emitter-io/emitter/internal/broker.(*Conn).Process
占用了93%的时间, 而这些gourotine大部分时间又是花在了Network Wait
上, 通过最外面一层的Network blocking profile(⬇) 又能看到具体的路径
这样去分析问题就快捷很多了。
一般都是log + profile 去定位问题,直接dbg/delve单步调试没有操作过。一般问题还可以通过 /debug/pprof/goroutine?debug=2 进行分析, 这里是所有go goutine的callstack dump 再加上这一栏的帮助的话应该轻松很多了。
这些进行一般都是直接入到prometheus, 然后进行监控+分析
原文: https://dave.cheney.net/2014/06/07/five-things-that-make-go-fast
Dave cheney在GoCon 2014 Tokyo 的一篇演讲。
由于golang不是基于vm的语言所以在数据结构存储上比python/java对内存友好很多,但是最后一条我不是太认同,尤其是举的最后一个
var Location [1000]Location
Inside the array, the Location structures are stored sequentially, rather than as pointers to 1,000 Location structures stored randomly. 这里数组在所有语言中应该都是连续存储的,并不是golang所独有的特点。
为了减少函数调用,将很小的函数直接inline化, 这个应该也是所有语言都有的特性
逃逸:变量生命周期超过声明的函数 如果没有发生逃逸,这个变量就不用在heap上分配,也就减少了GC的压力。
这里c虽然使用了new操作,但是作用域没有超出CenterCursor函数所有还是会在栈上分配。
yes, 这个才是go 快的最大特定 多进程时代
多线程时代 go routine时代
由于在多线程环境中所有线程都在同一个进程空间里,为了保护一个线程之间相互破坏需要放很多的guard page 线程越多这个gard page也就越多,这对内存资源来说是很多的浪费。go 在这方面的改进就是没有gard page,初始栈只有8k,然后随着需求的增加可以不断的申请。这里涉及到一个golang的栈管理相关的知识点。 在1.0~1.2中的操作是, 先给他个8k的栈,如果需要更大的空间的时候就新开一个栈,等返回之后把新的栈给释放掉。 如果有大量的goroutine做申请/释放操作的话就会引起hot split问题, 为了解决这个问题在1.3中引入了copy技术,如果一个goroutine需要更大的栈了就先申请一个更大栈,然后将所有老的栈里的东西拷贝过去,然后在新的栈里执行程序。 详细可以参考
https://blog.cloudflare.com/how-stacks-are-handled-in-go/ https://docs.google.com/document/d/1wAaf1rYoM4S4gtnPh0zOlGzWtrZFQ5suE8qr2sD8uWQ/pub
注: 图片来源于作者博客
原文: https://stackimpact.com/blog/practical-golang-benchmarks/
不多说,见过写benchmark最好的文章
目录
An Overview of Go's Tooling(2020.08.25) Profiling Go Programs(2020.08.26) Using Go as a scripting language in Linux(2020.08.27) Profiling and optimizing Go web applications(2020.08.28) Diagnostics(2020.08.29) Five things that make Go fast(2020.08.29) Practical Go Benchmarks(2020.08.29)