Go Gin是如何优化字符串渲染性能的?

点击上方蓝色“飞雪无情”关注我,设个星标,第一时间看文章

看Go Web框架Gin的Release,发现它1.7版本的Release有个增强型的更新:

chore: improve render string performance #2365

好奇心驱使打开了这个PR看了下,发现代码改动非常简单,只有一行:

// 旧代码
  _, err = io.WriteString(w, format)
  // 新代码
  _, err = w.Write([]byte(format))

真的就是这么简单的修改,把原来通过 io.WriteString 的实现换成了通过w.Write直接写入。更改文件可以参考这里 https://github.com/gin-gonic/gin/pull/2365/files。

这里看起来也是挺疑惑的,这样的改动会有提升吗?但是通过作者给的性能测试,是有些许提升的。我们来看看作者的性能测试结果(可以横向滚动)。

before

script:

$ go get github.com/gin-gonic/gin@v1.6.3
$ go test -bench=. -benchtime=10s

result:

BenchmarkGinStatic-8               23306             50344 ns/op            8283 B/op        157 allocs/op
BenchmarkGinGitHubAPI-8            16506             71542 ns/op           10801 B/op        203 allocs/op
BenchmarkGinGplusAPI-8            284379              4061 ns/op             685 B/op         13 allocs/op
BenchmarkGinParseAPI-8            153843              7657 ns/op            1361 B/op         26 allocs/op

after

script:

$ go get github.com/gin-gonic/gin@1bd5a8f
$ go test -bench=. -benchtime=10s

result:

BenchmarkGinStatic-8              246488             46740 ns/op            9915 B/op        314 allocs/op
BenchmarkGinGitHubAPI-8           176192             65348 ns/op           12940 B/op        406 allocs/op
BenchmarkGinGplusAPI-8           3298646              3747 ns/op             811 B/op         26 allocs/op
BenchmarkGinParseAPI-8           1683153              7003 ns/op            1620 B/op         52 allocs/op

每次操作花费的时间没有提升太多,反而每次操作分配的对象翻倍了

我感觉是不划算,即使想用内存空间换时间,这个换算方式也不划算。

现在我们对比下为什么会出现这种性能测试结果,首先看下这两个写入的实现代码:

// io.WriteString
func WriteString(w Writer, s string) (n int, err error) {
  if sw, ok := w.(StringWriter); ok {
    return sw.WriteString(s)
  }
  return w.Write([]byte(s))
}

旧的使用的是io.WriteString 方法,相比新的改动,多了StringWriter接口的断言,其实我们知道ResponseWriter是一个StringWriter,所以走的是sw.WriteString代码逻辑,不会走下面的w.Write方法。

如果你逐步的跟下去sw.WriteString的逻辑,会发现最终走的是bufio.Writer的WriteString方法,不会增加内存分配,但是这整条调用链很复杂,所以每次操作耗时会久一些。

但是w.Write([]byte(s)) 这个强制转换,是会多一次内存分配的。其实后来我看到了这段代码的再次优化,也验证了我的猜测。

_, err = w.Write(bytesconv.StringToBytes(format))

https://github.com/gin-gonic/gin/pull/2508/files

换成了这样,bytesconv.StringToBytes这是一个零内存分配的转换函数。

func StringToBytes(s string) []byte {
  return *(*[]byte)(unsafe.Pointer(
    &struct {
      string
      Cap int
    }{s, len(s)},
  ))
}

利用了unsafe.Pointer实现。

Gin的字符串优化过程是一个非常的经验示例,它也踩过坑,但是最终使用了正确的方法。

在Go SDK标准库中,有很多Write String,或者字符串拼接的函数方法,一定要选最适合自己代码那个。我以前写了几篇关于字符串拼接的文章,可以参考。

——  精彩推荐  ——

一步步提升Go语言生成随机字符串的效率

Go语言字符串高效拼接(三)

Go语言字符串高效拼接(二)

Go语言字符串高效拼接(一)

Go语言slice的本质-SliceHeader

1d083a82fa4c66c1bb5595451620be62.png

扫码关注

分享、点赞、在看就是最大的支持

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值