goroutine数目多的话,会不会像系统线程那样导致浪费很多时间在切换goroutine

有问必答ghost 回复了问题 • 5 人关注 • 3 个回复 • 151 次浏览 • 3 小时前 • 来自相关话题

Golang把模板里的左尖括号也给转义了?

有问必答tupunco 回复了问题 • 2 人关注 • 3 个回复 • 41 次浏览 • 6 小时前 • 来自相关话题

记一次golang的gzip优化

文章分享thinkboy 发表了文章 • 1 个评论 • 43 次浏览 • 7 小时前 • 来自相关话题

背景

近期使用Golang官方的"compress/gzip"包对数据压缩返回给App,此场景特性:数据不固定、高并发。在实际过程中发现一个简单逻辑的API服务,几百QPS的情况下CPU却很高达到几个核负载。查看全部
					

背景


近期使用Golang官方的"compress/gzip"包对数据压缩返回给App,此场景特性:数据不固定、高并发。在实际过程中发现一个简单逻辑的API服务,几百QPS的情况下CPU却很高达到几个核负载。

问题追踪


通过golang自带工具pprof抓图分析CPU,如下图(由于有业务代码,所以部分信息遮盖了):
http://img-hxy021.didistatic.com/static/way/do1_In6jEmpzPXAca5KnRNUD


通过此图可以看出,整个工程里有两个CPU消耗大头:1)GC高 2)大部分CPU耗在Gzip上.看方法属于New操作,再加上GC高,很容易往一个方向上去想,就是对象创建过多造成。


于是google搜了一些资料发现有人尝试优化gzip,地址:https://github.com/klauspost/compress/tree/master/gzip,但经过测试虽然速度提升20~30%,但是并不兼容原生Gzip,似乎并不是一个很通用的方案


分析源码


1.首先看下demo里原生的使用方式


demo地址:https://github.com/thinkboy/gzip-benchmark


func OldGzip(wr http.ResponseWriter, r *http.Request) {
buf := new(bytes.Buffer)
w := gzip.NewWriter(buf)

leng, err := w.Write(originBuff)
if err != nil || leng == 0 {
return
}
err = w.Flush()
if err != nil {
return
}
err = w.Close()
if err != nil {
return
}
b := buf.Bytes()
wr.Write(b)

// 查看是否兼容go官方gzip
/*gr, _ := gzip.NewReader(buf)
defer gr.Close()
rBuf, err := ioutil.ReadAll(gr)
if err != nil {
panic(err)
}
fmt.Println(string(rBuf))*/
}

2.其次看下官方gzip的实现,如下图:
http://img-hxy021.didistatic.com/static/way/do1_TIZjHEQ3BarilNCNC3Z8
http://img-hxy021.didistatic.com/static/way/do1_QvSBEc6YCpXRafIxvK9C


跟踪代码寻找几处与Pprof图相关的有New操作的地方,首先第一张图每次都会New一个Writer,然后在第二张图里的Write的时候,每次又都会为新创建的Writer分配一个压缩器。对于对象的反复创建有一个通用的思路,使用对象池。


3.尝试使用对象池


http://img-hxy021.didistatic.com/static/way/do1_HQTmIbkd8zBFJVKPmPwr


通过上图我们发现gzip的Writer有个Reset()方法,该方法调用的init()里的实现是如果已经存在压缩器,就复用并且Reset()。也就是说其实官方已经提供了一种方式让用户不再反复New Writer。然后我们可以这样改造下实现代码:


func MyGzip(wr http.ResponseWriter, r *http.Request) {
buf := spBuffer.Get().(*bytes.Buffer)
w := spWriter.Get().(*gzip.Writer)
w.Reset(buf)
defer func() {
// 归还buff
buf.Reset()
spBuffer.Put(buf)
// 归还Writer
spWriter.Put(w)
}()

leng, err := w.Write(originBuff)
if err != nil || leng == 0 {
return
}
err = w.Flush()
if err != nil {
return
}
err = w.Close()
if err != nil {
return
}
b := buf.Bytes()
wr.Write(b)

// 查看是否兼容go官方gzip
/*gr, _ := gzip.NewReader(buf)
defer gr.Close()
rBuf, err := ioutil.ReadAll(gr)
if err != nil {
panic(err)
}
fmt.Println(string(rBuf))*/
}

我们给压缩过程中用到的Buffer以及Writer定义对象池spBuffer、spWriter,然后每次api请求都从对象池里去取,然后Reset,从而绕过New操作。


这里容易产生一个疑问:对象池其实本身就是一个“全局大锁”,高并发场景下这把全局大锁影响有多大?(其实有一种深度优化的方式就是拆锁,比如依据某个ID进行取余取不同的对象池。这里就拿一把大锁来实验).


下面看一下此次改造后的压测结果(QPS: 3000):


不使用对象池(CPU 使用28个核左右):


http://img-hxy021.didistatic.com/static/way/do1_gmYIJXtQ7KNJtts9itST


使用对象池(CPU 使用22个核左右):


http://img-hxy021.didistatic.com/static/way/do1_mJGJQGHiQNYvumIZiqTx


通过CPU使用来看有消耗降低22%左右,由于QPS并不是很高,所以这里对象池的“全局大锁”的影响暂且可以忽略。


结论


针对官方Gzip的压缩可以使用对象池来改善。


klauspost所提供的方案也列举在demo中了,虽然属于自己改了压缩算法不兼容Golang官方包,但亲测对压缩速度也提升了很大百分比。使用该库+对象池的方式可能会达到更显著优化效果。


demo地址:https://github.com/thinkboy/gzip-benchmark

怎么用反射序列化一个struct,将其属性和值组合成一段字符

有问必答zhaohu 回复了问题 • 1 人关注 • 2 个回复 • 68 次浏览 • 9 小时前 • 来自相关话题

关于接口的一些问题

有问必答Memory_Leak 回复了问题 • 3 人关注 • 3 个回复 • 71 次浏览 • 9 小时前 • 来自相关话题

GoCN每日新闻(2017-07-25)

每日新闻Ezral 回复了问题 • 3 人关注 • 3 个回复 • 360 次浏览 • 11 小时前 • 来自相关话题

关于cookiejar用法的疑问

有问必答caoqianli 回复了问题 • 3 人关注 • 3 个回复 • 135 次浏览 • 12 小时前 • 来自相关话题

Beego run http server Running on http://:8080

有问必答astaxie 回复了问题 • 2 人关注 • 1 个回复 • 72 次浏览 • 15 小时前 • 来自相关话题

golang如何结束goroutine

有问必答plain 回复了问题 • 3 人关注 • 4 个回复 • 161 次浏览 • 16 小时前 • 来自相关话题

轻松筹-招聘Golang开发工程师(25到45k)

回复

招聘应聘jianan_song 发起了问题 • 1 人关注 • 0 个回复 • 190 次浏览 • 17 小时前 • 来自相关话题

有必要设置多个gopath吗?

有问必答cye 回复了问题 • 9 人关注 • 7 个回复 • 588 次浏览 • 18 小时前 • 来自相关话题

收到广告私信?

有问必答Hawken 回复了问题 • 6 人关注 • 6 个回复 • 529 次浏览 • 18 小时前 • 来自相关话题

GoCN每日新闻(2017-07-26)

回复

每日新闻astaxie 发起了问题 • 1 人关注 • 0 个回复 • 220 次浏览 • 19 小时前 • 来自相关话题

关于 swagger问题,bee run -gendoc=true

有问必答astaxie 回复了问题 • 3 人关注 • 2 个回复 • 102 次浏览 • 1 天前 • 来自相关话题

招聘Go工程师 坐标北京 薪资15-30K

回复

招聘应聘小井 发起了问题 • 1 人关注 • 0 个回复 • 151 次浏览 • 1 天前 • 来自相关话题