范型

Go 1.17中你就可以使用泛型了
编译的时候需要加-gcflags=-G=3参数,而当前master分支,默认已经支持泛型,不需要加-G=3参数了。










Go泛型的变化是增加了两个操作符: ~和


an approximation element ~T restricts to all types whose underlying type is T: 代表底层类型是T
a union element T1 | T2 | … restricts to any of the listed elements: 代表或,类型列表之一。
Go的泛型有别于其它语言的方案,在Go语言中泛型叫做Type Parameter(类型参数).



Taylor和Griesemer的提案Type Parameters Proposal更多的是泛型呈现形式和影响的思考,对具体的实现涉及甚少。



无论什么编程语言,根据Russ Cox的观察,实现泛型至少要面对下面三条困境之一,那还是在2009年:



拖累程序员:比如C语言,增加了程序员的负担,需要曲折的实现,但是不对增加语言的复杂性
拖累编译器: 比如C++编程语言,增加了编译器的负担,可能会产生很多冗余的代码,重复的代码还需要编译器斟酌删除,编译的文件可能非常大。Rust的泛型也属于这一类。
拖累执行时间:比如Java,将一些装箱成Object,进行类型擦除。虽然代码没啥冗余了,空间节省了,但是需要装箱拆箱操作,代码效率低。
很显然, Go语言至简的设计哲学让它的泛型实现不会选择增加程序员的负担的道路,所以它会在第二和第三种方案中做选择。虽然提案中没有最终说明它选择了哪种方案,但是从实际编译的代码可以看出,它选择的是第二种方案。

Keith H. Randall, MIT的博士,现在在Google/Go team做泛型方面的开发,提出了Go泛型实现的三个方案:
字典
在编译时生成一组实例化的字典,在实例话一个泛型函数的时候会使用字典进行蜡印(stencile)。



当为泛型函数生成代码的时候,会生成唯一的一块代码,并且会在参数列表中增加一个字典做参数,就像方法会把receiver当成一个参数传入。字典包含为类型参数实例化的类型信息。



字典在编译时生成,存放在只读的data section中。



当然字段可以当成第一个参数,或者最后一个参数,或者放入一个独占的寄存器。



当然这种方案还有依赖问题,比如字典递归的问题,更重要的是,它对性能可能有比较大的影响,比如一个实例化类型int, x=y可能通过寄存器复制就可以了,但是泛型必须通过memmove。



蜡印(Stenciling)
或者翻译成用模板印等。



就像下面的动图一样,同一个泛型函数,为每一个实例化的类型参数生成一套独立的代码,感觉和rust的泛型特化一样。



这种方案和上面的字典方案正好相反。



比如下面一个泛型方法:
func fT1, T2 any T2 {

}
如果有两个不同的类型实例化的调用:



1
2
var a float64 = fint, float64
var b struct{f int} = fcomplex128, struct{f int}
那么这个方案会生成两套代码:
func f1(x int, y int) float64 {
… identical bodies …
}
func f2(x int, y complex128) struct{f int} {
… identical bodies …
}
因为编译f时是不知道它的实例化类型的,只有在调用它时才知道它的实例化的类型,所以需要在调用时编译f。对于相同实例化类型的多个调用,同一个package下编译器可以识别出来是一样的,只生成一个代码就可以了,但是不同的package就不简单了,这些函数表标记为DUPOK,所以链接器会丢掉重复的函数实现。



这种策略需要更多的编译时间,因为需要编译泛型函数多次。因为对于同一个泛型函数,每种类型需要单独的一份编译的代码,如果类型非常多,编译的文件可能非常大,而且性能也比较差。



混合方案(GC Shape Stenciling)
混合前面的两种方案。



对于实例类型的shape相同的情况,只生成一份代码,对于shape类型相同的类型,使用字典区分类型的不同行为。



这种方案介于前两者之间。



啥叫shape?



类型的shape是它对内存分配器/垃圾回收器呈现的方式,包括它的大小、所需的对齐方式、以及类型哪些部分包含指针。



每一个唯一的shape会产生一份代码,每份代码携带一个字典,包含了实例化类型的信息。



这种方案的问题是到底能带来多大的收益,它会变得有多慢,以及其它的一些问题。



从当前的反编译的代码看,当前Go采用的是第二种方案,尽管名称中已经带了shape、dict的标志,或许,Go的泛型方案还在进化之中,进化到第三种方案或者其它方案也不是没有可能。



https://colobu.com/2021/08/30/how-is-go-generic-implemented/


Category golang