返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

8.1 实现

发布于 2024-10-12 19:15:48 字数 3832 浏览 0 评论 0 收藏 0

泛型的实现方式,通常有:

  • 模板(stenciling):为每次调用生成代码实例,即便类型参数相同。
  • 字典(dictionaries):单份代码实例,以字典传递类型参数信息。

模板方式性能最佳,但编译时间较长,且生成文件较大。
字典方式代码最少,但复杂度较高,且性能最差。

模板

Go 泛型实现介于两者之间,称作 GCShape stenciling with Dictionaries
任意指针类型,或具有相同底层类型(underlying type),属于同一 GCShape 组。

// above are same GCShape

type a int
type b int
type c = int

编译器为每个 GCShape 生成代码实例,并在每次调用时以字典传递类型信息。

package main

func test[T any](x T) {
	println(x)
}

func main() {
	test(1)

	// same underlying type
	type X int
	test(X(2))

	test("abc")
}
$ go build -gcflags "-l"
$ go tool objdump -S -s "main\.main" ./test

TEXT main.main(SB)
func main() {

	test(1)
  0x455214		LEAQ main..dict.test[int](SB), AX	
  0x45521b		MOVL $0x1, BX				
  0x455220		CALL main.test[go.shape.int](SB)	
  
	test(X(2))
  0x455225		LEAQ main..dict.test[main.X.1](SB), AX  ; 字典不同。
  0x45522c		MOVL $0x2, BX				
  0x455231		CALL main.test[go.shape.int](SB)	      ; 函数相同。
  
	test("abc")
  0x455236		LEAQ main..dict.test[string](SB), AX	
  0x45523d		LEAQ 0xc84c(IP), BX			
  0x455244		MOVL $0x3, CX				
  0x455249		CALL main.test[go.shape.string](SB)	   ; 新实例。
}


$ go tool objdump -s "main\.test" ./test

TEXT main.test[go.shape.int](SB)

  main.go:3		0x455274		MOVQ BX, 0x8(SP)			      ; 并未使用字典。
  main.go:4		0x455279		CALL runtime.printlock(SB)		
  main.go:4		0x45527e		MOVQ 0x8(SP), AX			
  main.go:4		0x455283		CALL runtime.printint(SB)		
  main.go:4		0x455288		CALL runtime.printnl(SB)		
  main.go:4		0x45528d		CALL runtime.printunlock(SB)		

TEXT main.test[go.shape.string](SB)

  main.go:3		0x4552d4		MOVQ CX, 0x30(SP)			
  main.go:3		0x4552d9		MOVQ BX, 0x28(SP)			
  main.go:4		0x4552de		NOPW					
  main.go:4		0x4552e0		CALL runtime.printlock(SB)		
  main.go:4		0x4552e5		MOVQ 0x28(SP), AX			
  main.go:4		0x4552ea		MOVQ 0x30(SP), BX			
  main.go:4		0x4552ef		CALL runtime.printstring(SB)		
  main.go:4		0x4552f4		CALL runtime.printnl(SB)		
  main.go:4		0x4552f9		CALL runtime.printunlock(SB)		

所有指针(任意类型)同组,与指针目标类型不同组。

func main() {
	a := 1
	test(a)
    test(&a)    // &int、&float 同组,与 int、float 不同组。

	b := 1.2
    test(&b) 
}
$ go build -gcflags "-l"
$ go tool objdump -S -s "main\.main" ./test

TEXT main.main(SB)
func main() {

	a := 1
  0x455214		MOVQ $0x1, 0x18(SP)	
  
	test(a)
  0x45521d		LEAQ main..dict.test[int](SB), AX	
  0x455224		MOVL $0x1, BX				
  0x455229		CALL main.test[go.shape.int](SB)	
  
	test(&a)
  0x45522e		LEAQ main..dict.test[*int](SB), AX	
  0x455235		LEAQ 0x18(SP), BX			
  0x45523a		CALL main.test[go.shape.*uint8](SB)	  ; 和 test(a) 不同。
  
	b := 1.2
  0x45523f		MOVSD_XMM $f64.3ff3333333333333(SB), X0	
  0x455247		MOVSD_XMM X0, 0x10(SP)			
  
	test(&b)
  0x45524d		LEAQ main..dict.test[*float64](SB), AX	
  0x455254		LEAQ 0x10(SP), BX			
  0x455259		CALL main.test[go.shape.*uint8](SB)	  ; 和 test(&a) 相同。
}


$ go tool objdump -s "main\.test" ./test

TEXT main.test[go.shape.int](SB)
TEXT main.test[go.shape.*uint8](SB)

字典

基于字典的动态调用细节。禁用优化,以免内联后丢失中间细节。

package main

type Tester interface {
    test()
    string() string
}

type N struct{}
func (N) test() { println("test!") }
func (N) string() string { return "N!" }

func test[T Tester](x T) {
    s := x.string()
    println(s)
}

func main() {
    test(N{})
}

从结果看,不管是字典还是方法表调用都像简化的接口实现。

$ go build -gcflags "-N -l -S"

main.main STEXT
	0x000e LEAQ	main..dict.test[main.N](SB), AX
	0x0015 CALL	main.test[go.shape.struct {}](SB)

main..dict.test[main.N] SRODATA
	0x0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
	rel 0+8 t=1  main.N.string+0
	rel 0+0 t=23 type:main.N+0
	rel 8+8 t=1  type:main.N+0

main.test[go.shape.struct {}] STEXT
	0x0015 MOVQ	(AX), CX
	0x001b CALL	CX

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文