曹大带我学 Go(10)—— 如何给 Go 提性能优化的 pr
之前写了一篇《成为 Go Contributor》 的文章,讲了如何给 Go 提一个 typo 的 pr,以此熟悉整个流程。当然,离真正的 Contributor 还差得远。
开课前曹大在 Go 夜读上讲了他给 Go 提的一个关于 tls 的性能优化,课上又细讲了下,本文就带大家来学习下他优化了啥以及如何看优化效果。
第一次提的 pr 在这里,之后又挪到了一个新的位置,前后有一些代码上的简化,最后看着挺舒服。
优化前每个 tls 连接上都有一个 write buffer
,但是活跃的连接数很少,很多内存都被闲置了,这种就可以用 sync.Pool 来优化了。
用 sync.Pool 缓存 []byte
,并顺带将连接上的一个 outBuf 字段给干掉了:
整体上改动挺少,效果也不错。
虽然一开始给了 _test
文件,但其实并不能太好反映性能的提升。因此后面曹大又写了一个简单的 client 和 server 来实际测试。
我在开发机上测了一下,优化还是挺明显的。这又是一个使用 pprof 查看性能优化的好例子。
client
的代码如下:
1package main
2
3import (
4 "crypto/tls"
5 "fmt"
6 "io/ioutil"
7 "net/http"
8 "os"
9 "strconv"
10 "sync"
11
12 "go.uber.org/ratelimit"
13)
14
15func main() {
16 url := os.Args[3]
17 connNum, err := strconv.ParseInt(os.Args[1], 10, 64)
18 if err != nil {
19 fmt.Println(err)
20 return
21 }
22
23 qps, err := strconv.ParseInt(os.Args[2], 10, 64)
24 if err != nil {
25 fmt.Println(err)
26 return
27 }
28
29 bucket := ratelimit.New(int(qps))
30
31 var l sync.Mutex
32 connList := make([]*http.Client, connNum)
33
34 for i := 0; ; i++ {
35 bucket.Take()
36 i := i
37 go func() {
38 l.Lock()
39 if connList[i%len(connList)] == nil {
40 connList[i%len(connList)] = &http.Client{
41 Transport: &http.Transport{
42 TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
43 IdleConnTimeout: 0,
44 MaxIdleConns: 1,
45 MaxIdleConnsPerHost: 1,
46 },
47 }
48 }
49 conn := connList[i%len(connList)]
50 l.Unlock()
51 if resp, e := conn.Get(url); e != nil {
52 fmt.Println(e)
53 } else {
54 defer resp.Body.Close()
55 ioutil.ReadAll(resp.Body)
56 }
57 }()
58 }
59
60}
逻辑比较简单,就是固定连接数、固定 QPS 向服务端发请求。
server
的代码如下:
1package main
2
3import (
4 "fmt"
5 "net/http"
6 _ "net/http/pprof"
7)
8
9var content = make([]byte, 16000)
10
11func sayhello(wr http.ResponseWriter, r *http.Request) {
12 wr.Header()["Content-Length"] = []string{fmt.Sprint(len(content))}
13 wr.Header()["Content-Type"] = []string{"application/json"}
14 wr.Write(content)
15}
16
17func main() {
18 go func() {
19 http.ListenAndServe(":3333", nil)
20 }()
21 http.HandleFunc("/", sayhello)
22
23 err := http.ListenAndServeTLS(":4443", "server.crt", "server.key", nil)
24 if err != nil {
25 fmt.Println(err)
26 }
27}
逻辑也很简单,起了一个 tls server,并注册了一个 sayhello 接口。
启动 server 后,先用 1.15(1.17 之前的版本都可以,曹大的改动还没合入)测试:
查看 server 的内存 profile。后面还会用 --base
的命令,比较前后两个 profile 文件的差异。
pprof
的命令如下:
1go tool pprof --http=:8000 http://127.0.0.1:3333/debug/pprof/heap
看看这个大“平顶山”,有那味了(平顶山表示可以优化,如果是那种特别窄的尖尖就没办法了)~
因为这个 pr 已经合到了 1.17,我们再用 1.17 来测一下:
为了使用 --base
命令来进行比较,需要把 profile 文件保存下来:
1curl http://127.0.0.1:3333/debug/pprof/heap > mem.1.14
2curl http://127.0.0.1:3333/debug/pprof/heap > mem.1.17
最后来比较优化前后的差异:
1go tool pprof -http=:8000 --base mem.1.15 mem.1.17
优化效果还是很明显的。我们来看菜单栏里的 view->top
:
整个优化从最终的提交来看还挺简单,但是能发现问题所在,并能结合自己的知识储备进行优化还是挺难的。我们平时也要多积累相关的优化经验,到关键时候才能顶上去。像 pprof 的使用,要自己多加练习。
- 原文作者:饶全成
- 原文链接:https://qcrao.com/post/go-tls-pr-by-xargin/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。