0%

好惨,搞个 cgo 提案被教育了

前一阵又搞了个 cgo 提案,但是,被轮番教训了…

也不知道哪里勾起来他们的兴趣,一度让我怀疑是遇到了杠精,哈哈,可能对面的哥们也是一脸无语 …

背景

Golang 默认对于从 Go 传入到 C 的内存会进行检查,如果内存中有 Golang 指针(指向 Golang GC 管理的内存),就会报 panic。

但是,我们在 Envoy Go 扩展里,为了性能和使用的便捷性,并不想要有这个检查,所以我们是依赖 GODEBUG=cgocheck=0 这个环境来关闭检查。

虽然 Go1.21 引入了 runtime.Pinner,可以通过显式 Pin 的方式,将这些指针也传入 C。

提案

但是,上一次搞 cgo 内存优化 的时候,又想到了一个更好关闭这个检查的方式:

通过 #cgo nocheckpointer functionName 这种编译指令,来函数级别的关闭检查。

这个对我们来说,是更好的选择,上一次也写过好处:

  1. 可以在 C 函数级别指定生效,影响域足够小
  2. 可以写在 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,总结这么几个点:

  1. 目前的规则,就是考虑了未来,为 GC 实现移动对象留下空间
  2. 虽然现在可以直接将指针传给 C,未来实现移动 GC 的时候,cgo 编译器会为这些指针生成 Pin 代码,让 GC 不移动这些指针
  3. 但是,不允许指针指向的内存中再含有指针,是一个折中考量。这种情况出现的不多,如果以后也自动 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 大佬说了算了。