初学golang(一个月多),以前主要用其余语言,若有问题欢迎指出。html
go get github.com/uber/go-torch # 再安装 brendangregg/FlameGraph export PATH=$PATH:/absolute/path/FlameGraph-master # 还须要安装一个graphviz用来画内存图 yum install graphviz
import "net/http" import _ "net/http/pprof" func main() { // 主函数中添加 go func() { http.HandleFunc("/program/html", htmlHandler) // 用来查看自定义的内容 log.Println(http.ListenAndServe("0.0.0.0:8080", nil)) }() }
# 用 -u 分析CPU使用状况 ./go-torch -u http://127.0.0.1:8080 # 用 -alloc_space 来分析内存的临时分配状况 ./go-torch -alloc_space http://127.0.0.1:8080/debug/pprof/heap --colors=mem # 用 -inuse_space 来分析程序常驻内存的占用状况; ./go-torch -inuse_space http://127.0.0.1:8080/debug/pprof/heap --colors=mem # 画出内存分配图 go tool pprof -alloc_space -cum -svg http://127.0.0.1:8080/debug/pprof/heap > heap.svg
使用浏览器查看svg文件,程序运行中,能够登陆 http://127.0.0.1:10086/debug/pprof/
查看程序实时状态git
在此基础上,能够经过配置handle
来实现自定义的内容查看,能够添加Html格式的输出,优化显示效果github
func writeBuf(buffer *bytes.Buffer, format string, a ...interface{}) { (*buffer).WriteString(fmt.Sprintf(format, a...)) } func htmlHandler(w http.ResponseWriter, req *http.Request) { io.WriteString(w, statusHtml()) } // 访问 localhost:8080/program/html 能够看到一个表格,一秒钟刷新一次 func statusHtml() string { var buf bytes.Buffer buf.WriteString("<html><meta http-equiv=\"refresh\" content=\"1\">" + "<body><h2>netflow-decoder status count</h2>" + "<table width=\"500px\" border=\"1\" cellpadding=\"5\" cellspacing=\"1\">" + "<tr><th>NAME</th><th>TOTAL</th><th>SPEED</th></tr>") writeBuf(&buf, "<tr><td>UDP</td><td>%d</td><td>%d</td></tr>", total, speed) ... buf.WriteString("</table></body></html>") return buf.String() }
火焰图自下而上是函数的调用关系,底下的一个方块是入口,对应其上面的方块是他直接或者间接调用到的,长度是运行时所占用的CPU时长,颜色没有特别的意义golang
从上到下是调用关系,如箭头所示,表示给每一个函数【累计】分配了多少内存,包括它本身占用多少以及向下调用时分配了多少。从这个就能够看出程序中哪一个地方最消耗内存,最底下没有名字的方块是这个函数内,每次向系统申请内存的大小数组
实际图片是svg格式的,能够无限方法,这里只是看个大概(人为打码)。浏览器
先说一下结论吧,性能限制主要是IO相关的,好比网络数据收发、磁盘读写等,在程序复杂度并无那么高的状况下,调优只是锦上添花,主要能够帮助本身更好的了解这个语言。 如下调优的部分主要是针对项目中,从github上引入的部分代码bash
先来看看先后的对比图:网络
调优前,两个蓝色方框中的函数分别是StringDefault
和Read
,前者的做用是把二进制表示的数值转为对应大小的字符串([]byte -> int -> string),后者是将二进制读到指定的位置,这两个函数占用了40%+的时间。优化后以下:app
因为对前面两个部分的优化,占用的时间已经大大缩小,从70%左右降低到40%+。后面没有作处理的网络读写net.Write
占了不少时间svg
首先针对字符串处理部分,从第一个火焰图点击StringDefault
能够看到细节,以下图:
里面有不少的read
和newobject
,从函数功能上咱们能够知道,转换一次字符串并不须要这么麻烦,来看看代码是这么写的:
// 原来的写法,以双字节为例 // 先申请临时变量,将字节数组转为buffer(多余且费时费内存),再读取到临时变量中,再进行类型转换 var n uint16 binary.Read(bytes.NewBuffer(b), binary.BigEndian, &n) return strconv.Itoa(int(n)) // 精简后:直接讲字节数组转为对应长度的int,再转为字符串便可 return strconv.Itoa(int(binary.BigEndian.Uint16(b)))
同理,针对第一个火焰图的Read
,定位到代码以下:
// 逐字节读取 binary.Read(每次新建一个临时变量,并读取一个字节,总共须要分红n次读取) // 目的是为了将每一个直接按大端解析, n := recordSize for n > 0 { var field uint8 if err := binary.Read(buffer, binary.BigEndian, &field); err != nil { return 0, err } Fields = append(Fields, field) n -= 1 } // 然而实际上单字节内不论是网络数据仍是内存中的数据都是同样的,大小端主要是针对多字节的状况,好比int类型的四个字节。 // 换成一次拷贝用 buffer.Next(n) 函数,直接把n个直接拷贝到对应位置 Fields = buffer.Next(recordSize)
这两个简单的修改就提高了不少性能。。因而可知,在从github上抄代码时(¬_¬),特别是一些不知名的代码仍是要本身审阅一遍。。
最终让程序性能获得重大提高的,是对最后net.Write
的优化。这个方法也很简单,原来是每条消息发一次包,改为拼接多条短消息,再发一个大包,大包的长度不要超过一个以太帧,本文使用UDP是不超过1450,预留了一点空间,反正也放不下一条消息。
内存调优主要是使用上面那个pprof图,观察流程是否合理,是否能够简化,以及每一个函数的内存分配状况,具体过程不像上面那么清洗,都是小修小补,故直接总结一些可能不够可靠的经验: