一、 使用 sync.Pool 减少 GC 压力,提升性能
简单讲下go的gc,它的核心原理就是三色标记法和写屏障,可以实现优秀并发处理。gc一般不会频繁调用,他是根据GOGC的值来判断,具体就是上次触发GC后总堆值大于等于上次的(1+GOGC/100)倍,就会触发gc。所以为了不出现gc频繁调用损耗性能,一般会采用:增大GOGC值(得测试不能随便调);尽量减少堆的产生:1.使用栈变量 2. 使用sync.pool 对象池 的办法,这里讲sync.pool
为什么sync.pool可以减少gc压力:
sync.pool 对象池的变量是重复使用的,高频持续创建销毁的场景,它用的内存一直都是对象池开辟的那一块,所以不会导致总堆增大很多;
常见场景:缓冲缓存区
代码示例:
var requestBodyPool = sync.Pool{New: func() interface{} {return bytes.NewBuffer(make([]byte, 0, 1024)) // 预分配 1KB 的初始容量},
}func Handler(w http.ResponseWriter, r *http.Request) {// 从池中获取一个 Buffer,并断言为*bytes.Bufferbuf := requestBodyPool.Get().(*bytes.Buffer)buf.Reset() // 重置数据,防止脏数据污染// 确保在处理完毕后将其放回池中defer func() {requestBodyPool.Put(buf) // 回收数据}()// ... 处理结果并返回响应
}
要注意的坑点:
1.数据取出来要断言,断言有panic的风险(直接封装一个 sync.pool的结构体,能避免这个问题)
2.数据取出来后一定要 重置,不然就是脏数据
3. 最好 采用 defer xxxpool.Put(buf)的方法,不然中间panic了数据没回收
4. 高频持续的场景才适用 sync.pool,不然效果不大甚至反效果
5. 如果pool存的是切片,需要回收的时候可以判断切片大小,太大的切片可以直接放弃put。因为put之后切片再对象池不会被回收,总堆会升高,这样也会导致频繁gc