GoLang底层|GoLang之Go1.17泛型


文章目录

  • GoLang之Go1.17泛型

GoLang之Go1.17泛型
我们在代码中经常会用到一些本地缓存组件,它们是复用性极高的基础组件。在使用体验上跟map 差不多,都提供了set 和get 方法。为了支持任意类型,这些方法都使用了空接口类型的参数,内部实际存储数据的是个值类型为空接口的map。
GoLang底层|GoLang之Go1.17泛型
文章图片

GoLang底层|GoLang之Go1.17泛型
文章图片

使用set 方法时,一般不会觉得有什么不方便,因为从具体类型或接口类型到空接口的赋值不需要进行额外处理。但是get 方法使用时,我们需要通过类型断言,把取出来的数据转化成预期的类型。比如我想从本地缓存c 里面取出来一个string,那就需要这样写。如果仅仅是多出这一步操作的话,也无可厚非,可实际上并不是这么简单。
GoLang底层|GoLang之Go1.17泛型
文章图片

我们知道空接口本质上是一对指针,用来装载值类型的话会发生装箱,造成变量逃逸。
GoLang底层|GoLang之Go1.17泛型
文章图片

例如我们用Cache 来缓存int64类,缓存对象c底层是个map,我们已经介绍过在map 的bucket 中存储着元素的哈希值,key 和value。对于c 而言,bucket 这里存储的value 是一个一个的空接口。而实际的int64会在堆上单独分配空接口的data 指针指向堆上的int64。相较于直接把int64存储在map 的bucket 里,这里凭空多出来了一次堆分配,而且还多占用了两倍的内存空间。
GoLang底层|GoLang之Go1.17泛型
文章图片

面对这些问题,我们基于Go1.17中的泛型。改造一下上面的cache缓存。
改造的时候要注意几点,第一,要把缓存类型的首字母改成小写。因为目前go1.17的泛型实现还不支持导出,泛型相关的类型和函数,只能在当前包中使用。
第二1.17的泛型支持默认是关闭的,构建可执行文件的时候,记得这样指定参数来显示的开启。而且据观察,build 命令只有在编译main包的时候才会透传这个参数,这就限制了我们只能在main中使用泛型,改造好以后,同样用来存储int64类型的数值。
GoLang底层|GoLang之Go1.17泛型
文章图片

然后我们通过反射看一下它的底层map 存储的是什么类型的元素。这段代码会输出"int64 ",也就是说泛型缓存cache 的底层map 会直接在bucket 中存储int64类型的数值。没有额外的堆分配,看起来泛型还真解决了这里的问题,不过这也不是完全没有代价的。
GoLang底层|GoLang之Go1.17泛型
文章图片

GoLang底层|GoLang之Go1.17泛型
文章图片

先来看泛型到底是怎么实现的。为了方便进一步分析,我们构建可执行文件的时候,关闭编译器的内联,优化使用go build 命令编译完成后,通过nm 命令分析可执行文件可以看到编译器。可以看到编译器为cache[int64]类型生成了两个方法get 和set,也就是说"var c cache[inyt64]"才是真正可用的完整的类型,而它是抽象的,只是模板而已。
GoLang底层|GoLang之Go1.17泛型
文章图片

所以使用泛型最直接的代价就是编译器会为使用同一套模板的每个类型都生成一套代码,可能导致可执行文件大小有所增加。
GoLang底层|GoLang之Go1.17泛型
文章图片

而且即便使用泛型,要想在一个缓存对象里面存储多种不同类型的值,依然要使用空接口。否则一个缓存对象里就只能存储一种类型的值。所以说范型本质上是编译阶段的代码生成,它并不会替代空接口。空接口主要用来实现语言的动态特性,它们的适用场景根本不同。
GoLang底层|GoLang之Go1.17泛型
文章图片

GoLang底层|GoLang之Go1.17泛型
文章图片

【GoLang底层|GoLang之Go1.17泛型】关于go1.17的泛型尝鲜就先到这里,更多内容请移步公众号或知乎同名账号查看泛型相关文章。

    推荐阅读