前一阵又搞了个 cgo 提案,但是,被轮番教训了…
也不知道哪里勾起来他们的兴趣,一度让我怀疑是遇到了杠精,哈哈,可能对面的哥们也是一脸无语 …
背景
Golang 默认对于从 Go 传入到 C 的内存会进行检查,如果内存中有 Golang 指针(指向 Golang GC 管理的内存),就会报 panic。
但是,我们在 Envoy Go 扩展里,为了性能和使用的便捷性,并不想要有这个检查,所以我们是依赖 GODEBUG=cgocheck=0
这个环境来关闭检查。
虽然 Go1.21 引入了 runtime.Pinner
,可以通过显式 Pin
的方式,将这些指针也传入 C。
提案
但是,上一次搞 cgo 内存优化 的时候,又想到了一个更好关闭这个检查的方式:
通过 #cgo nocheckpointer functionName
这种编译指令,来函数级别的关闭检查。
这个对我们来说,是更好的选择,上一次也写过好处:
- 可以在 C 函数级别指定生效,影响域足够小
- 可以写在 Envoy Go 的源码里,完全不需要用户关心
所以,有了这个提案:
https://github.com/golang/go/issues/62425
分歧
很快,还没等到官方回应,就有人出来教育我了。
提炼一下有效的分歧是:只有 cgocheck=0
才能运行的代码,是否是安全的
经过的一系列的正反方的拉锯:
反方:依赖 Go 内置类型的内存布局是未定义的行为,不靠谱
正方:cgo 会生成 _GoString_
这些 struct 给 C 来使用,依赖是合理的
反方:没有 Pin
的内存,在 C 使用的时候,可能已经被 GC 释放掉了
正方:引用关系还在的,我们在当前这次 C 函数使用是安全的
反方:那 GC 还有可能移动对象呢
正方:至少目前的 GC 是不会移动的,Go 这种指针暴露给用户了的,大概率以后的 GC 也不会搞成移动的;就算以后搞了,大概率也不好保证完全向后兼容,既然到时候会破坏向后兼容,那破坏下这一个编译指令,也不是什么大事了
反方:累了,懒得跟你说了 …
收场
刚开始的时候,ian 大佬还把这个提案,放到了 Incoming proposal 里。
但是,经过一个周末的来回 pk,ian 大佬还是出手把这个给关了。
给出的理由是:making it easier to break the rules
,也就是希望更多保持现有规则。
至于规则的原因,甩了一个当时他设计这个规则时的讨论贴:
https://github.com/golang/go/issues/12416
原因
过了一遍 issue,总结这么几个点:
- 目前的规则,就是考虑了未来,为 GC 实现移动对象留下空间
- 虽然现在可以直接将指针传给 C,未来实现移动 GC 的时候,cgo 编译器会为这些指针生成
Pin
代码,让 GC 不移动这些指针 - 但是,不允许指针指向的内存中再含有指针,是一个折中考量。这种情况出现的不多,如果以后也自动
Pin
的话,可能导致实现比较复杂
好吧,这回算是搞清楚了。
这个 cgocheck
检查就是为了未来的移动 GC 而预留的,所以,关闭 cgocheck
检查,至少现在还是安全的。
虽然从我个人的角度看,以后改成移动 GC 的可能性不大,但是官方大佬并不希望给自己埋雷。
填坑 runtime.Pinner
好吧,既然不让搞,那就还是老老实实用 runtime.Pinner
吧。
对于提案中,提到的 runtime.Pinner
的坑:
Pin
指针的时候,指针必须指向 Go GC 中的地址,如果不是的话,会直接 panic。
这个让使用 Pin
变得很难的,比如,常量 string 的 data 指针,就直接指向 rodata
段中的内存,这个作为普通用户是很难判断的。
刚好看到也有个 issue 在抱怨,rsc 大佬说,这种情况可以直接忽略,于是又搞了这个补丁:
https://go-review.googlesource.com/c/go/+/527156
好在这个改动没啥分歧,比较快就被合并了。
Envoy Go
至此,应该可以基于 runtime.Pinner
来实现 cgocheck clean 的 Envoy Go extension 了。
至少默认情况下,可以不依赖于用户手动设置 GODEBUG
环境变量了,毕竟,很多时候一不小心就忘了,而且很多时候,或许大家也不是那么的关心这一丢性能。
对于一些将 string 指针传给 C 的,可能就老老实实的改成 data 指针 + 长度,分开两个参数来搞。对于复杂的传参,那就用 runtime.Pinner
吧。
至于,是否提供一个可选的编译指令,来直接跳过 runtime.Pinner
的开销,这个还得后面再压测一把看看的了。
最后
哈哈,虽然这个提案被喷惨了,不过好歹咱也是玩过微博,混过社区的人,这点破事也算不上啥。
虽然没人喜欢被怼,但是,折腾一波,也搞清楚了 cgocheck
的前因后果,也不白折腾。
咱也就不纠结了,毕竟这种折中取舍的事情,主要看投票权的,估计也就是 ian 大佬说了算了。