- GOGCTRACE = 1. / my_go_program 2 > log_file
- gc16(8): 34+6+5 ms, 367 -> 365 MB 817253 -> 782045 (18216892-17434847) objects, 64(2182) handoff, 72(22022) steal, 553/244/51 yields
- type MyData1 struct {
- next *MyData
- Id int
- Name string
- }
- var mydata1 *MyData1
- type MyData2 struct {
- Id int
- Name string
- }
- var mydata2 []MyData2
- import "C"
- import "unsafe"
- ...
- var theCArray *TheCType := C.getTheArray()
- length := C.getTheArrayLength()
- var theGoSlice []TheCType
- sliceHeader := (*reflect.SliceHeader)((unsafe.Pointer(&theGoSlice)))
- sliceHeader.Cap = length
- sliceHeader.Len = length
- sliceHeader.Data = uintptr(unsafe.Pointer(&theCArray[0]))
- // now theGoSlice is a normal Go slice backed by the C array
于是我将内存数据库用到的 slice 类型全部换成自己用 C 伪造的 slice,还好当初内存数据库用的是代码生成器,否则代码就要改死掉了 :)
全部替换完后,我拿外网同样数据对比,优化前的程序 GC 扫描时间 100 多 ms,对象数量 140 万,优化后的程序 GC 扫描时间 18ms,对象数量 16 万。
本来可以就这样打完收功了,但是生活总是充满戏剧性,内网测试的时候发现好友列表里面的名字全乱码了,肯定跟优化有关系,但为什么会乱码呢?
我的推测是 go 构造的字符串对象被 C 构造的对象引用,这样的引用导致 go 把字符串对象当成没人使用,于是就被回收利用了。
我只好把所有字符串字段也全部改为 C 伪造的对象,原理给伪造 slice 是一样的,不同的是字符串用 StringHeader 表示。
经过改造,字符串再也不会乱码了,不过需要很小心的释放内存。
优化过程中 Go 提供的 pprof 模块起到了很重要的作用,所有的优化都是以数据为依据的,如果不能看到数据就没有办法定位问题。
程序中可以用 pprof.Lookup("heap") 来获得堆信息,其中包含了对象数量和 GC 执行时间等有用的数据。
上次群里有人问 map[int]XXX 这样的数据结构是否会有 GC 问题,正好这个数据结构我之前也考虑过,也在上面的数据结构实验里体现了,map[int]XXX 和 map[int]XXX 是一样的,一条数据就是一个对象,对 GC 是否有影响取决于对象的数量。
从上面的观测数值来看百来万的对象数量所造成的暂停应该还不足以影响程序,除非应用场景对实时性要求非常高。
但是对于游戏这样的常驻内存程序来说,对象的增长速度和对象数量上限也需要留意,比如刚开始对象数量只有几万,随着日子增长,玩家数据增多,对象数量达到百万千万,那时候可能就会有影响了。
之前第一次优化过后正好有人在知乎问 Go 的 GC 情况,我回了一帖,里面有比较详细的第一次优化的数据,大家可以参考一下:点击查看
Go 的垃圾回收机制在实践中有哪些需要注意的地方?
来源: http://it.taocms.org/12/2341.htm