Go语言 使用CGo进行优化

转载自达达的博客


前阵子我利用cgo对游戏内存数据库的数据存储方式做了优化,减少了对象数量。但是程序放到线上环境后出现了段错误,直接导致进程退出,只好临时又把优化的部分去掉,去掉后程序又继续稳定运行了两周。

优化代码撤下来后,我重新整理了代码。整理下来,我觉得对含有字符串字段的表的优化逻辑太过复杂了,并且很难控制边界情况。

这里举个例子:

type MyTable struct {
    Name string
}

func InsertMyTable(myTable MyTable) {
    nameLen := C.size_t(len(myTable.Name))
    name := C.calloc(1,nameLen)
    C.memcpy(name,unsafe.Pointer((*reflect.StringHeader)(unsafe.Pointer(&myTable.Name)).Data),nameLen)
    (*reflect.StringHeader)(unsafe.Pointer(&myTable.Name)).Data = uintptr(name)
}

func UpdateMyTable(myTable MyTable) {
    nameLen := C.size_t(uintptr(name)

    C.free(unsafe.Pointer((*reflect.StringHeader)(unsafe.Pointer(&oldMyTable.Name)).Data))
}

func DeleteMyTable(myTable MyTable) {
    C.free(unsafe.Pointer((*reflect.StringHeader)(unsafe.Pointer(&myTable.Name)).Data))
}

上面的代码是对项目中遇到的问题的模拟,不是真实代码,真实代码其实比这个要复杂,因为对象会被用于事务提交,还需要控制对象在事务提交后才能释放字符串类型字段,在更新时还需要判断字符串是否有变更等等。

为什么需要对字符串进行处理呢?因为如果不对字符串进行处理的话,当go的字符串被赋值给cgo创建的内存块后,go并不不清楚字符串被引用,从而导致有用的字符串被gc回收。

同样的道理也适用于嵌套的结构,例如:

struct {
    ChildTable *MyChildTable
}

如果一个go创建的MyChildTable对象被赋值给一个cgo维护的MyTable对象的ChildTable字段,go的gc是跟踪不到这个引用关系的,这时候会出现MyTable对象还有效的时候,内部的ChildTable字段所引用的go对象已经被回收,如果程序访问ChildTable对象,就会出现段错误

但是子表的情况是比较好处理的,只要原来new(MyChildTable)的地方替换为自己实现的newMyChildTable(),用cgo来申请内存,自己手工释放,就不会有问题,边界情况也没有字符串那么多。代码像这样:

sizeofMyChildTable := unsafe.SizeOf(MyChildTable{})

func newMyChildTable() *MyChildTable {
    return (*MyChildTable)(C.calloc 排查段错误很困难,所以我想先做排除法,首先去掉了最复杂的字符串优化逻辑,含有字符串类型字段的内存表都不进行优化。还好游戏中字符串用得不多,只有少数几个表有用到字符串,稍微降低优化效果提高程序稳定性,还是划算的。

去掉字符串优化后的新版程序,已经稳定允许了一周,算是正式验证了cgo进行GC优化的有效性。

相关文章

程序目录结构 简单实现,用户登录后返回一个jwt的token,下次请求带上token请求用户信息接口并返回信息...
本篇博客的主要内容是用go写一个简单的Proof-of-Work共识机制,不涉及到网络通信环节,只是一个本地的简...
简介 默克尔树(MerkleTree)是一种典型的二叉树结构,其主要特点为: 最下面的叶节点包含存储数据或其...
接下来学习并发编程, 并发编程是go语言最有特色的地方, go对并发编程是原生支持. goroutine是go中最近本...
先普及一下, 什么是广度优先搜索 广度优先搜索类似于树的层次遍历。从图中的某一顶点出发,遍历每一个顶...
第一天: 接口的定义和实现 第二天: 一. go语言是面向接口编程. 在学习继承的时候说过, go语言只有封装,...