原创分享 Golang 泛型初探

PureWhiteWu for 字节跳动 · 2021年03月09日 · 最后由 astaxie 回复于 2021年03月10日 · 776 次阅读
本帖已被设为精华帖!

Golang 的泛型实现已经正式合并到 master 分支上啦,之后也会在 master 分支上进行开发,那么作为期待这个 feature 许久的 gopher,也想第一时间看看到底是如何实现的。

语法

这里不过多讲解泛型的语法,具体可以参考一下 https://github.com/golang/go/issues/43651 这个 issue。简单来说,在 struct 和 func 的名字后面可以加一个 [] 里面包含泛型的名字和限制条件,比如:

type container[T any] struct{
    elem T
}

any 是个特殊的关键字,表示所有类型都可以。

示例程序

这里我们写一个示例程序来编译成汇编,来看看泛型到底是怎么实现的:

package main

type Stringer interface {
  String() string
}

type Stringer2 interface {
  Stringer
}

type container[T Stringer] struct {
  s T
}

type stringerImpl struct {
  s string
}

func (s stringerImpl) String() string {
  return s.s
}

func loop[T any](s []T) {
  for _, v := range s {
    _ = v
  }
}

func main() {
  loop([]int{1, 2, 3, 4, 5})

  c := container[Stringer2]{}
  loop([]container[Stringer2]{c})
}

编译成汇编

我们先基于 master 分支来编译一个 go 出来,然后用这个 go 来执行以下命令:

$ go build -gcflags="-G=3 -l -S" main.go > main.s 2>&1

接下来去main.s这个文件看看,就会发现有这么一段代码:

"".#loop[int] STEXT nosplit size=18 args=0x18 locals=0x0 funcid=0x0
  0x0000 00000 (/Users/purewhite/go/src/local/study/main.go:39) TEXT  "".#loop[int](SB), NOSPLIT|ABIInternal, $0-24
  0x0000 00000 (/Users/purewhite/go/src/local/study/main.go:39) FUNCDATA  $0, gclocals·1a65e721a2ccc325b382662e7ffee780(SB)
  0x0000 00000 (/Users/purewhite/go/src/local/study/main.go:39) FUNCDATA  $1, gclocals·69c1753bd5f81501d95132d08af04464(SB)
  0x0000 00000 (/Users/purewhite/go/src/local/study/main.go:33) MOVQ  "".s+16(SP), AX
  0x0005 00005 (/Users/purewhite/go/src/local/study/main.go:33) XORL  CX, CX
  0x0007 00007 (/Users/purewhite/go/src/local/study/main.go:33) JMP 12
  0x0009 00009 (/Users/purewhite/go/src/local/study/main.go:33) INCQ  CX
  0x000c 00012 (/Users/purewhite/go/src/local/study/main.go:33) CMPQ  AX, CX
  0x000f 00015 (/Users/purewhite/go/src/local/study/main.go:33) JGT 9
  0x0011 00017 (/Users/purewhite/go/src/local/study/main.go:33) RET
  0x0000 48 8b 44 24 10 31 c9 eb 03 48 ff c1 48 39 c8 7f  H.D$.1...H..H9..
  0x0010 f8 c3                                            ..
"".#loop[container[Stringer2]] STEXT nosplit size=21 args=0x18 locals=0x0 funcid=0x0
  0x0000 00000 (/Users/purewhite/go/src/local/study/main.go:44) TEXT  "".#loop[container[Stringer2]](SB), NOSPLIT|ABIInternal, $0-24
  0x0000 00000 (/Users/purewhite/go/src/local/study/main.go:44) FUNCDATA  $0, gclocals·1a65e721a2ccc325b382662e7ffee780(SB)
  0x0000 00000 (/Users/purewhite/go/src/local/study/main.go:44) FUNCDATA  $1, gclocals·69c1753bd5f81501d95132d08af04464(SB)
  0x0000 00000 (/Users/purewhite/go/src/local/study/main.go:33) MOVQ  "".s+16(SP), AX
  0x0005 00005 (/Users/purewhite/go/src/local/study/main.go:33) TESTQ AX, AX
  0x0008 00008 (/Users/purewhite/go/src/local/study/main.go:33) JLE 20
  0x000a 00010 (/Users/purewhite/go/src/local/study/main.go:33) XORL  CX, CX
  0x000c 00012 (/Users/purewhite/go/src/local/study/main.go:33) INCQ  CX
  0x000f 00015 (/Users/purewhite/go/src/local/study/main.go:33) CMPQ  AX, CX
  0x0012 00018 (/Users/purewhite/go/src/local/study/main.go:33) JGT 12
  0x0014 00020 (/Users/purewhite/go/src/local/study/main.go:33) RET
  0x0000 48 8b 44 24 10 48 85 c0 7e 0a 31 c9 48 ff c1 48  H.D$.H..~.1.H..H
  0x0010 39 c8 7f f8 c3                                   9....

再看 main 中调用的地方:

  0x008c 00140 (/Users/purewhite/go/src/local/study/main.go:39) CALL  "".#loop[int](SB)
...
  0x00c0 00192 (/Users/purewhite/go/src/local/study/main.go:44) CALL  "".#loop[container[Stringer2]](SB)

基本可以确定,go 的泛型目前的实现方案是在编译时进行代码生成,这个方案虽然会降低编译速度,但是在运行时是没有性能损耗的。

更多原创文章干货分享,请关注公众号
  • 加微信实战群请加微信(注明:实战群):gocnio
Orichalcum GoCN 每日新闻 (2021-03-10) 中提及了此贴 03月10日 06:15
astaxie 将本帖设为了精华贴 03月10日 14:06
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册