您现在的位置是:亿华云 > 域名

如何给 Go 提性能优化的 pr

亿华云2025-10-03 20:09:00【域名】3人已围观

简介本文转载自微信公众号「码农桃花源」,作者小X。转载本文请联系码农桃花源公众号。你好,我是小X。曹大最近开 Go 课程了,小X 正在和曹大学 Go。这个系列会讲一些从课程中学到的让人醍醐灌顶的东西,拨云

本文转载自微信公众号「码农桃花源」,何给化作者小X。提性转载本文请联系码农桃花源公众号。何给化

你好,提性我是何给化小X。

曹大最近开 Go 课程了,提性小X 正在和曹大学 Go。何给化

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

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

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

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

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

conn

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

files changed

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

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

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

client 的代码如下:

package main import (  "crypto/tls"  "fmt"  "io/ioutil"  "net/http"  "os"  "strconv"  "sync"  "go.uber.org/ratelimit" ) func main() {   url := os.Args[3]  connNum, err := strconv.ParseInt(os.Args[1], 10, 64)  if err != nil {    fmt.Println(err)   return  }  qps, err := strconv.ParseInt(os.Args[2], 10, 64)  if err != nil {    fmt.Println(err)   return  }  bucket := ratelimit.New(int(qps))  var l sync.Mutex  connList := make([]*http.Client, connNum)  for i := 0; ; i++ {    bucket.Take()   i := i   go func() {     l.Lock()    if connList[i%len(connList)] == nil {      connList[i%len(connList)] = &http.Client{       Transport: &http.Transport{        TLSClientConfig:     &tls.Config{ InsecureSkipVerify: true},       IdleConnTimeout:     0,       MaxIdleConns:        1,       MaxIdleConnsPerHost: 1,      },     }    }    conn := connList[i%len(connList)]    l.Unlock()    if resp, e := conn.Get(url); e != nil {      fmt.Println(e)    } else {      defer resp.Body.Close()     ioutil.ReadAll(resp.Body)    }   }()  } } 

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

server 的代码如下:

package main import (  "fmt"  "net/http"  _ "net/http/pprof" ) var content = make([]byte, 16000) func sayhello(wr http.ResponseWriter, r *http.Request) {   wr.Header()["Content-Length"] = []string{ fmt.Sprint(len(content))}  wr.Header()["Content-Type"] = []string{ "application/json"}  wr.Write(content) } func main() {   go func() {    http.ListenAndServe(":3333", nil)  }()  http.HandleFunc("/", sayhello)  err := http.ListenAndServeTLS(":4443", "server.crt", "server.key", nil)  if err != nil {    fmt.Println(err)  } } 

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

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

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

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

pprof 的命令如下:

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

Go 1.15 mem profile

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

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

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

Go 1.17 mem profile

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

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

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

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

--base

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

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

很赞哦!(28777)