关于 Go 接口你从未想知道的事情·mcyoung

这是一篇关于 Go 语言实现细节的系列笔记文章,主要内容如下:

  • GC Shapes:Go 中类型布局的基本概念是类型的“形状”,这是 Go 垃圾回收器的一个实现细节,通过unsafe包暴露。可以使用unsafe.Sizeofunsafe.Alignof查询类型的大小和对齐方式,两者共同构成类型的“布局”。类型的“形状”还记录了其中包含指针的部分,因为垃圾回收器需要知道哪些部分是需要跟踪的指针。
  • Slices and Strings

    • Go 公开了切片和字符串的布局,切片是一个包含数据指针、长度和容量的结构体,字符串的布局与[]byte类似,但有容量限制。
    • 可以使用unsafe将切片别名到字符串,只要字符串被访问时切片不被修改就很安全,但要注意一些限制,如包含对齐填充的类型不能使用,包含指针的类型可能导致指针不可达,不可比较的类型和接口将通过地址比较。
    • 可以使用反射创建动态大小的数组类型,但调用refl.Interface()会进行数组的复制,因为数组类型的t.IfaceIndr()为真,会触发复制操作。
  • The Layout of Go’s Interfaces

    • Go 的接口布局在runtime2.go文件中,iface是通常的接口布局,由itab(接口表)和数据指针组成,efaceany的布局,直接存储类型可以减少类型切换时的指针加载。
    • 接口函数调用的生成代码比较复杂,包括检查接口是否为 nil,加载ITab.Fun和调用函数等操作。接口的上下转换也有不同的实现,下转换需要动态构建itab,可能会比较慢且可能导致 panic。
    • 类型切换使用类似的缓存机制,PGO 可以用于预填充类型断言缓存,但编译器中未找到相关代码。
  • Function Pointers:Go 的函数指针布局比较奇怪,通过runtime.funcval结构体表示,调用函数时先将函数指针解释为*funcval并加载fn字段,捕获的变量通过rdx寄存器访问。返回函数引用时返回全局函数地址,方法需要创建 thunk 来处理接收者指针的问题,可以通过添加捕获字段或使垃圾回收器忽略rdx中的指针来解决,但会增加二进制大小或带来安全风险。
  • Conclusion:文章旨在记录作者在 Go 语言中遇到的一些细节,供自己参考,同时提到了一些关于SizeofAlignof、GC 程序、汇编示例和类型断言缓存更新等方面的注意事项。
阅读 8
0 条评论