当前位置: 首页 > 科技观察

真实环境下大内存 Go 服务性能优化一例

时间:2023-03-20 10:24:12 科技观察

真实环境大内存Go服务性能优化实例转载本文请联系董泽润技术笔记公众号。这篇文章来自我之前的案例。之前很多人在公开会议上分享过这个案例,感觉有印象的同学不要喷。虽然是冷食,但是原创,干货满满^_^有时候不理解现象,明明调用RPC的时候设置了timeout超时,但是Grafna看到P99延迟很高,为什么???不要犹豫,或者超时设置不合理,比如只设置了单个socket超时,而断路器外层没有设置超时。你真的了解timeout[1]还有一种情况是GC在作怪。我们知道GoGC使用的是三色标记的方法。当GC压力较大时,用户态goroutine需要assist来协助标记对象。同时,如果GCSTWtime很高的话,那么业务的latency会比timeout大很多。服务使用go1.7,需要加载大量机器学习词汇,标准的Go大内存服务。优化前,延迟非常高,可以看到最大达到了2s,同时查看GCPauseNS也很吓人,基本接近1s,业务处理不可用。如何开启pprof这里就不写了。网上有很多。可以自己查看gotoolpprofbin/dupsdchttp://127.0.0.1:6060/debug/pprof/profile可以看到runtime.greyobject,runtime.mallocgc,runtime.heapBitsForObject,runtime.scanobject,runtime.memmoveTOPGC相关的CPU消耗6gotoolpprof-inuse_objectshttp://127.0.0.1:6060/debug/pprof/heap再次查看常驻对象数,发现1kw常驻内存对象(现在已经很少了,不多了),这些都是通过加载的小对象优化对象thevocabulary词汇表主要使用了两种类型,map[int64][]float32和map[string]int我们来看看三色标记,其实质就是递归扫描所有的指针类型,遍历判断是否被引用,那么问题来了,什么是指针类型???所有显示*T并且内部有指针的对象都是指针类型,例如map[int64][]float32因为值是一个切片并且内部包含指针。如果map有1kw个元素,那么GC还要递归描述所有的key/value才能理解这些,优化方法就在这里,把map[int64][]float32改成map[int64][6]float32,这里slice就变成了6个元素的数组,业务可以接受同时将map[string]int中的key由string类型改为int枚举值上线后的优化效果可以很明显的看到。常驻内存对象从1kw减少到200w,cpupprof也能看到。第一个是syscall,GC相关的减少了很多。检查Grafana外设IO延迟的减少非常明显。整体优化效果不错。这里也有例外,比如map的内部实现。如果key/value值类型的大小超过128字节,就会退化成一个指针128MAXELEMSIZE=128)//bmapmakesthemapbuckettypegiventhetypeofthemap.funcbmap(t*types.Type)*types.Type{ift.MapType().Bucket!=nil{returnt.MapType().Bucket}bucket:=types.New(TSTRUCT)keytype:=t.Key()elemtype:=t.Elem()dowidth(keytype)dowidth(elemtype)ifkeytype.Width>MAXKEYSIZE{keytype=types.NewPtr(keytype)}ifelemtype.Width>MAXELEMSIZE{elemtype=types.NewPtr(elemtype)}field:=make([]*types.Field,0,5)……}思考各个版本的性能的Go有很大的改进。go1.71kw对象服务压力很大,而我们公司的go1.152kw对象没有优化,没有压力。Go非常显着地优化了吞吐量。再次声明,本文仅作为GC性能分析的参考,请勿提前优化。另一方面也说明围棋三色标并非适用于所有场景。这次分享的大词汇量的常驻内存就是一个典型的例子:明明是Oldobjects,不用每次都需要GC去扫描,在这里羡慕java的分代GC