如何给 Go 提性能优化的 pr

时间:2022-10-04 00:23:48

如何给 Go 提性能优化的 pr

你好,我是小X。

曹大最近开 Go 课程了,小X 正在和曹大学 Go。

这个系列会讲一些从课程中学到的让人醍醐灌顶的东西,拨云见日,带你重新认识 Go。

之前写了一篇《成为 Go Contributor》 的文章,讲了如何给 Go 提一个 typo 的 pr,以此熟悉整个流程。当然,离真正的 Contributor 还差得远。

开课前曹大在 Go 夜读上讲了他给 Go 提的一个关于 tls 的性能优化,课上又细讲了下,本文就带大家来学习下他优化了啥以及如何看优化效果。

第一次提的 pr 在这里,之后又挪到了一个新的位置,前后有一些代码上的简化,最后看着挺舒服。

优化前每个 tls 连接上都有一个 write buffer,但是活跃的连接数很少,很多内存都被闲置了,这种就可以用 sync.Pool 来优化了。

如何给 Go 提性能优化的 pr

conn

用 sync.Pool 缓存 []byte,并顺带将连接上的一个 outBuf 字段给干掉了:

如何给 Go 提性能优化的 pr

files changed

整体上改动挺少,效果也不错。

虽然一开始给了 _test 文件,但其实并不能太好反映性能的提升。因此后面曹大又写了一个简单的 client 和 server 来实际测试。

我在开发机上测了一下,优化还是挺明显的。这又是一个使用 pprof 查看性能优化的好例子。

client 的代码如下:

  1. package main 
  2.  
  3. import ( 
  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. func main() { 
  15.  url := os.Args[3] 
  16.  connNum, err := strconv.ParseInt(os.Args[1], 10, 64) 
  17.  if err != nil { 
  18.   fmt.Println(err) 
  19.   return 
  20.  } 
  21.  
  22.  qps, err := strconv.ParseInt(os.Args[2], 10, 64) 
  23.  if err != nil { 
  24.   fmt.Println(err) 
  25.   return 
  26.  } 
  27.  
  28.  bucket := ratelimit.New(int(qps)) 
  29.  
  30.  var l sync.Mutex 
  31.  connList := make([]*http.Client, connNum) 
  32.  
  33.  for i := 0; ; i++ { 
  34.   bucket.Take() 
  35.   i := i 
  36.   go func() { 
  37.    l.Lock() 
  38.    if connList[i%len(connList)] == nil { 
  39.     connList[i%len(connList)] = &http.Client{ 
  40.      Transport: &http.Transport{ 
  41.       TLSClientConfig:     &tls.Config{InsecureSkipVerify: true}, 
  42.       IdleConnTimeout:     0, 
  43.       MaxIdleConns:        1, 
  44.       MaxIdleConnsPerHost: 1, 
  45.      }, 
  46.     } 
  47.    } 
  48.    conn := connList[i%len(connList)] 
  49.    l.Unlock() 
  50.    if resp, e := conn.Get(url); e != nil { 
  51.     fmt.Println(e) 
  52.    } else { 
  53.     defer resp.Body.Close() 
  54.     ioutil.ReadAll(resp.Body) 
  55.    } 
  56.   }() 
  57.  } 
  58.  

逻辑比较简单,就是固定连接数、固定 QPS 向服务端发请求。

server 的代码如下:

  1. package main 
  2.  
  3. import ( 
  4.  "fmt" 
  5.  "net/http" 
  6.  _ "net/http/pprof" 
  7.  
  8. var content = make([]byte, 16000) 
  9.  
  10. func sayhello(wr http.ResponseWriter, r *http.Request) { 
  11.  wr.Header()["Content-Length"] = []string{fmt.Sprint(len(content))} 
  12.  wr.Header()["Content-Type"] = []string{"application/json"
  13.  wr.Write(content) 
  14.  
  15. func main() { 
  16.  go func() { 
  17.   http.ListenAndServe(":3333", nil) 
  18.  }() 
  19.  http.HandleFunc("/", sayhello) 
  20.  
  21.  err := http.ListenAndServeTLS(":4443""server.crt""server.key", nil) 
  22.  if err != nil { 
  23.   fmt.Println(err) 
  24.  } 

逻辑也很简单,起了一个 tls server,并注册了一个 sayhello 接口。

启动 server 后,先用 1.15(1.17 之前的版本都可以,曹大的改动还没合入)测试:

  1. go run server.go 
  2.  
  3. # 1000 个连接,100 个 QPS 
  4. go run client.go 1000 100 https://localhost:4443 

查看 server 的内存 profile。后面还会用 --base 的命令,比较前后两个 profile 文件的差异。

pprof 的命令如下:

  1. go tool pprof --http=:8000 http://127.0.0.1:3333/debug/pprof/heap 

如何给 Go 提性能优化的 pr

Go 1.15 mem profile

看看这个大“平顶山”,有那味了(平顶山表示可以优化,如果是那种特别窄的尖尖就没办法了)~

因为这个 pr 已经合到了 1.17,我们再用 1.17 来测一下:

  1. go1.17rc1 run server.go 
  2. go1.17rc1 run client.go 1000 100 https://localhost:4443 

如何给 Go 提性能优化的 pr

Go 1.17 mem profile

为了使用 --base 命令来进行比较,需要把 profile 文件保存下来:

  1. curl http://127.0.0.1:3333/debug/pprof/heap > mem.1.14 
  2. curl http://127.0.0.1:3333/debug/pprof/heap > mem.1.17 

最后来比较优化前后的差异:

  1. go tool pprof -http=:8000 --base mem.1.15 mem.1.17 

如何给 Go 提性能优化的 pr

--base

优化效果还是很明显的。我们来看菜单栏里的 view->top:

如何给 Go 提性能优化的 pr

view->top

整个优化从最终的提交来看还挺简单,但是能发现问题所在,并能结合自己的知识储备进行优化还是挺难的。我们平时也要多积累相关的优化经验,到关键时候才能顶上去。像 pprof 的使用,要自己多加练习。

好了,这就是今天全部的内容了~ 我是小X,我们下期再见~

参考资料

[1]tls 的性能优化: https://www.bilibili.com/video/BV1Z64y1m7uc

[2]这里: https://go-review.googlesource.com/c/go/+/263277

[3]位置: https://go-review.googlesource.com/c/go/+/267957

原文链接:https://mp.weixin.qq.com/s/8R9ZLa0Xlscw6qhl-5g2Cg