泛型
泛型是程序设计语言的一种范式或抽象,实现对算法的一种抽象,避免不同数据结构在实现相同算法时重复编码问题,允许在算法实例化时指定算法元素的类型。
golang在1.18实现了对泛型的支持。
生效阶段
泛型作用在编译阶段,编译器会根据泛型的定义和具体调用场景,进行实例化实现;
性能开销
泛型会产生一定的性能开销,泛型算法逻辑越简单性能影响越明显(测试案例:1000000000次a+b求和,原生代码耗时300ms左右,泛型代码耗时1.5s,开销是原生代码的5倍左右),函数复杂时性能开销接近(测试案例:1000000000次fmt.Sprintf操作,原生代码耗时,泛型代码耗时2m8.14s)。
实现原理
编译器在编译阶段,会根据程序对泛型调用场景,生成对应类型的实例化函数,通过CALL调用具体实例化后的函数;
示例:
// 泛型代码
func addT[T int|int64|floag64](a ,b T) T{
return a+b
}
// 原生代码
func addI(a, b int) int{
return a+b
}
func addF(a, b float64) float64{
return a+b
}
func addI64(a, b int64) int64{
return a+b
}
1)泛型实例化
1.1)对应addT[int](1, 2)调用,实例化函数汇编
main.addT[go.shape.int_0] STEXT dupok nosplit size=61 args=0x18 locals=0x10 funcid=0x0 align=0x0
0x0000 00000 (/generic.go:3)TEXTmain.addT[go.shape.int_0](SB), DUPOK|NOSPLIT|ABIInternal, $16-24
0x0000 00000 (/generic.go:3)SUBQ$16, SP
0x0004 00004 (/generic.go:3)MOVQBP, 8(SP)
0x0009 00009 (/generic.go:3)LEAQ8(SP), BP
0x000e 00014 (/generic.go:3)FUNCDATA$0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
0x000e 00014 (/generic.go:3)FUNCDATA$1, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
0x000e 00014 (/generic.go:3)FUNCDATA$5, main.addT[go.shape.int_0].arginfo1(SB)
0x000e 00014 (/generic.go:3)MOVQAX, main..dict+24(SP)
0x0013 00019 (/generic.go:3)MOVQBX, main.a+32(SP)
0x0018 00024 (/generic.go:3)MOVQCX, main.b+40(SP)
0x001d 00029 (/generic.go:3)MOVQ$0, main.~r0(SP)
0x0025 00037 (/generic.go:4)MOVQmain.a+32(SP), AX
0x002a 00042 (/generic.go:4)ADDQmain.b+40(SP), AX
0x002f 00047 (/generic.go:4)MOVQAX, main.~r0(SP)
0x0033 00051 (/generic.go:4)MOVQ8(SP), BP
0x0038 00056 (/generic.go:4)ADDQ$16, SP
0x003c 00060 (/generic.go:4)RET
0x0000 48 83 ec 10 48 89 6c 24 08 48 8d 6c 24 08 48 89 H...H.l$.H.l$.H.
0x0010 44 24 18 48 89 5c 24 20 48 89 4c 24 28 48 c7 04 D$.H.\$ H.L$(H..
0x0020 24 00 00 00 00 48 8b 44 24 20 48 03 44 24 28 48 $....H.D$ H.D$(H
0x0030 89 04 24 48 8b 6c 24 08 48 83 c4 10 c3 ..$H.l$.H....
1.2)原生代码addI对应汇编
main.addI STEXT nosplit size=56 args=0x10 locals=0x10 funcid=0x0 align=0x0
0x0000 00000 (/normal_pure.go:3) TEXT main.addI(SB), NOSPLIT|ABIInternal, $16-16
0x0000 00000 (/normal_pure.go:3) SUBQ $16, SP
0x0004 00004 (/normal_pure.go:3) MOVQ BP, 8(SP)
0x0009 00009 (/normal_pure.go:3) LEAQ 8(SP), BP
0x000e 00014 (/normal_pure.go:3) FUNCDATA $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
0x000e 00014 (/normal_pure.go:3) FUNCDATA $1, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
0x000e 00014 (/normal_pure.go:3) FUNCDATA $5, main.addI.arginfo1(SB)
0x000e 00014 (/normal_pure.go:3) MOVQ AX, main.a+24(SP)
0x0013 00019 (/normal_pure.go:3) MOVQ BX, main.b+32(SP)
0x0018 00024 (/normal_pure.go:3) MOVQ $0, main.~r0(SP)
0x0020 00032 (/normal_pure.go:4) MOVQ main.a+24(SP), AX
0x0025 00037 (/normal_pure.go:4) ADDQ main.b+32(SP), AX
0x002a 00042 (/normal_pure.go:4) MOVQ AX, main.~r0(SP)
0x002e 00046 (/normal_pure.go:4) MOVQ 8(SP), BP
0x0033 00051 (/normal_pure.go:4) ADDQ $16, SP
0x0037 00055 (/normal_pure.go:4) RET
0x0000 48 83 ec 10 48 89 6c 24 08 48 8d 6c 24 08 48 89 H...H.l$.H.l$.H.
0x0010 44 24 18 48 89 5c 24 20 48 c7 04 24 00 00 00 00 D$.H.\$ H..$....
0x0020 48 8b 44 24 18 48 03 44 24 20 48 89 04 24 48 8b H.D$.H.D$ H..$H.
0x0030 6c 24 08 48 83 c4 10 c3 l$.H....
1.3)两者之间的差异,泛型相对原生多了一行:0x000e 00014 (/generic.go:3)MOVQAX, main..dict+24(SP)
2)泛型调用
2.1)泛型调用实例化
main.main STEXT size=87 args=0x0 locals=0x20 funcid=0x0 align=0x0
0x0000 00000 (/generic.go:7) TEXT main.main(SB), ABIInternal, $32-0
0x0000 00000 (/generic.go:7) CMPQ SP, 16(R14)
0x0004 00004 (/generic.go:7) PCDATA $0, $-2
0x0004 00004 (/generic.go:7) JLS 80
0x0006 00006 (/generic.go:7) PCDATA $0, $-1
0x0006 00006 (/generic.go:7) SUBQ $32, SP
0x000a 00010 (/generic.go:7) MOVQ BP, 24(SP)
0x000f 00015 (/generic.go:7) LEAQ 24(SP), BP
0x0014 00020 (/generic.go:7) FUNCDATA $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
0x0014 00020 (/generic.go:7) FUNCDATA $1, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
0x0014 00020 (/generic.go:8) LEAQ main..dict.addT[int](SB), AX
0x001b 00027 (/generic.go:8) MOVL $1, BX
0x0020 00032 (/generic.go:8) MOVL $2, CX
0x0025 00037 (/generic.go:8) PCDATA $1, $0
0x0025 00037 (/generic.go:8) CALL main.addT[go.shape.int_0](SB)
0x002a 00042 (/generic.go:10) LEAQ main..dict.addT[float64](SB), AX
0x0031 00049 (/generic.go:10) MOVSD $f64.3ff0000000000000(SB), X0
0x0039 00057 (/generic.go:10) MOVSD $f64.4000000000000000(SB), X1
0x0041 00065 (/generic.go:10) CALL main.addT[go.shape.float64_0](SB)
0x0046 00070 (/generic.go:11) MOVQ 24(SP), BP
0x004b 00075 (/generic.go:11) ADDQ $32, SP
0x004f 00079 (/generic.go:11) RET
0x0050 00080 (/generic.go:11) NOP
0x0050 00080 (/generic.go:7) PCDATA $1, $-1
0x0050 00080 (/generic.go:7) PCDATA $0, $-2
0x0050 00080 (/generic.go:7) CALL runtime.morestack_noctxt(SB)
0x0055 00085 (/generic.go:7) PCDATA $0, $-1
0x0055 00085 (/generic.go:7) JMP 0
0x0000 49 3b 66 10 76 4a 48 83 ec 20 48 89 6c 24 18 48 I;f.vJH.. H.l$.H
0x0010 8d 6c 24 18 48 8d 05 00 00 00 00 bb 01 00 00 00 .l$.H...........
0x0020 b9 02 00 00 00 e8 00 00 00 00 48 8d 05 00 00 00 ..........H.....
0x0030 00 f2 0f 10 05 00 00 00 00 f2 0f 10 0d 00 00 00 ................
0x0040 00 e8 00 00 00 00 48 8b 6c 24 18 48 83 c4 20 c3 ......H.l$.H.. .
0x0050 e8 00 00 00 00 eb a9 .......
rel 23+4 t=14 main..dict.addT[int]+0
rel 38+4 t=7 main.addT[go.shape.int_0]+0
rel 45+4 t=14 main..dict.addT[float64]+0
rel 53+4 t=14 $f64.3ff0000000000000+0
rel 61+4 t=14 $f64.4000000000000000+0
rel 66+4 t=7 main.addT[go.shape.float64_0]+0
rel 81+4 t=7 runtime.morestack_noctxt+0
2.2)原生代码调用实例化
main.main STEXT nosplit size=103 args=0x0 locals=0x38 funcid=0x0 align=0x0
0x0000 00000 (/normal_pure.go:11) TEXT main.main(SB), NOSPLIT|ABIInternal, $56-0
0x0000 00000 (/normal_pure.go:11) SUBQ $56, SP
0x0004 00004 (/normal_pure.go:11) MOVQ BP, 48(SP)
0x0009 00009 (/normal_pure.go:11) LEAQ 48(SP), BP
0x000e 00014 (/normal_pure.go:11) FUNCDATA $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
0x000e 00014 (/normal_pure.go:11) FUNCDATA $1, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
0x000e 00014 (/normal_pure.go:12) MOVQ $1, main.a+40(SP)
0x0017 00023 (/normal_pure.go:12) MOVQ $2, main.b+24(SP)
0x0020 00032 (<unknown line number>) NOP
0x0020 00032 (/normal_pure.go:4) MOVQ main.a+40(SP), AX
0x0025 00037 (/normal_pure.go:4) ADDQ $2, AX
0x0029 00041 (/normal_pure.go:12) MOVQ AX, main.~R0+8(SP)
0x002e 00046 (/normal_pure.go:12) JMP 48
0x0030 00048 (/normal_pure.go:14) MOVSD $f64.3ff0000000000000(SB), X0
0x0038 00056 (/normal_pure.go:14) MOVSD X0, main.a+32(SP)
0x003e 00062 (/normal_pure.go:14) MOVSD $f64.4000000000000000(SB), X0
0x0046 00070 (/normal_pure.go:14) MOVSD X0, main.b+16(SP)
0x004c 00076 (<unknown line number>) NOP
0x004c 00076 (/normal_pure.go:8) MOVSD main.a+32(SP), X1
0x0052 00082 (/normal_pure.go:8) ADDSD X0, X1
0x0056 00086 (/normal_pure.go:14) MOVSD X1, main.~R0(SP)
0x005b 00091 (/normal_pure.go:14) JMP 93
0x005d 00093 (/normal_pure.go:15) MOVQ 48(SP), BP
0x0062 00098 (/normal_pure.go:15) ADDQ $56, SP
0x0066 00102 (/normal_pure.go:15) RET
0x0000 48 83 ec 38 48 89 6c 24 30 48 8d 6c 24 30 48 c7 H..8H.l$0H.l$0H.
0x0010 44 24 28 01 00 00 00 48 c7 44 24 18 02 00 00 00 D$(....H.D$.....
0x0020 48 8b 44 24 28 48 83 c0 02 48 89 44 24 08 eb 00 H.D$(H...H.D$...
0x0030 f2 0f 10 05 00 00 00 00 f2 0f 11 44 24 20 f2 0f ...........D$ ..
0x0040 10 05 00 00 00 00 f2 0f 11 44 24 10 f2 0f 10 4c .........D$....L
0x0050 24 20 f2 0f 58 c8 f2 0f 11 0c 24 eb 00 48 8b 6c $ ..X.....$..H.l
0x0060 24 30 48 83 c4 38 c3 $0H..8.
rel 52+4 t=14 $f64.3ff0000000000000+0
rel 66+4 t=14 $f64.4000000000000000+0
2.3)两者之间的差异
2.3.1)泛型调用之前,会多做一些判断操作
0x0000 00000 (/generic.go:7)CMPQSP, 16(R14)
0x0004 00004 (/generic.go:7)PCDATA$0, $-2
0x0004 00004 (/generic.go:7)JLS80
0x0006 00006 (/generic.go:7)PCDATA$0, $-1
0x0014 00020 (/generic.go:8)LEAQmain..dict.addT[int](SB), AX
2.3.2)泛型调用使用 CALL执行实例化函数执行,原生代码使用JMP跳转到实例化函数执行
0x0025 00037 (/generic.go:8)CALLmain.addT[go.shape.int_0](SB)