sync.Pool不如上面的方法节省内存,但好处是可以缩容 。
数量可控的中型对象有的时候,我们可能需要一些定额数量的对象,并且对这些对象复用 。
这时可以使用channel来做内存池 。需要时从channel取出,用完放回channel 。
slice的复用fasthttp, VictoriaMetrics等组件的作者 valyala可谓是把slice复用这个技巧玩上了天,具体可以看fasthttp主页上的Tricks with []byte
buffers这部分介绍 。
概要的总结起来就是:[]byte这样的数组分配后,不要释放,然后下次使用前,用slice=slice[:0]
来清空,继续使用其上次分配好的cap指向的空间 。
这篇中文的总结也非常不错:《fasthttp对性能的优化压榨》
valyala大神还写了个 bytebufferpool,对[]byte
重用的场景进行了封装 。
避免容器空间动态增长对于slice和map而言,在预先可以预估其空间占用的情况下,通过指定大小来减少容器操作期间引起的空间动态增长 。特别是map,不但要拷贝数据,还要做rehash操作 。
func xxx(){ slice := make([]byte, 0, 1024) // 有的时候,golangci-lint会提示未指定空间的情况 m := make(map[int64]struct{}, 1000)}大神技巧:用slice代替map此技巧源于valyala大神 。
假设有一个很小的map需要插入和查询,那么把所有key-value顺序追加到一个slice中,然后遍历查找——其性能损耗可能比分配map带来的GC消耗还要小 。
- map变成slice,少了很多动态调整的空间
- 如果整个slice能够塞进CPU cache line,则其遍历可能比从内存load更加快速
避免栈逃逸golang中非常酷的一个语法特点就是没有堆和栈的区别 。编译器会自动识别哪些对象该放在堆上,哪些对象该放在栈上 。
func xxx() *ABigStruct{ a := new(ABigStruct) // 看起来是在堆上的对象 var b ABigStruct // 看起来是栈上的对象 // do something // not return a // a虽然是对象指针,但仅限于函数内使用,所以编译器可能把a放在栈上 return &b // b超出了函数的作用域,编译器会把b放在堆上 。}valyala大神的经验:先找出程序的hot path,然后在hot path上做栈逃逸的分析 。尽量避免hot path上的堆内存分配,就能减轻GC压力,提升性能 。
fasthttp首页上的介绍:这篇文章介绍了侦测栈逃逸的方法:
Fast HTTP package for Go. Tuned for high performance. Zero memory allocations in hot paths. Up to 10x faster than net/http
验证某个函数的变量是否发生逃逸的方法有两个:
? testProj go run -gcflags "-m -l" internal/test1/main.go# command-line-argumentsinternal/test1/main.go:4:2: moved to heap: ainternal/test1/main.go:5:11: main make([]*int, 1) does not escape
- go run -gcflags "-m -l" (-m打印逃逸分析信息,-l禁止内联编译);例:
? testProj go tool compile -S internal/test1/main.go | grep newobject 0x0028 00040 (internal/test1/main.go:4) CALL runtime.newobject(SB)
- go tool compile -S main.go | grep runtime.newobject(汇编代码中搜runtime.newobject指令,该指令用于生成堆对象),例:
经验总结扩展阅读
- 持续集成指南:GitLab 的 CI/CD 工具配置与使用
- 信用卡怎么查消费明细
- 消费贷款申请产生的费用高吗
- oppo账号的姓名怎么修改
- 租房可以换锁芯吗合法么 租房换锁费用谁来承担
- 特斯拉可以用家用电充电吗
- 拆线多久可以用祛疤膏?
- 卫生间和厨房用什么瓷砖?
- 肉苁蓉副作用是什么
- 计米器怎么设置参数