前言
asonginterfaceinterfaceinterfaceinterfaceGointerface{}interface{}
类型断言的基本使用
Type Assertioninterface valuex.(T)xinterface typeTasserted type
func main() {
var demo interface{} = "Golang梦工厂"
str := demo.(string)
fmt.Printf("value: %v", str)
}
demodemonildemoTstrpanicpanic
number := demo.(int64)
fmt.Printf("value: %v\n", number)
所以为了安全起见,我们还可以这样使用:
func main() {
var demo interface{} = "Golang梦工厂"
number, ok := demo.(int64)
if !ok {
fmt.Printf("assert failed")
return
}
fmt.Printf("value: %v\n", number)
}
运行结果:assert failed
t,ok:=i.(T)(i) nil(i)T tok trueTpanicok false t T
type switch
func main() {
var demo interface{} = "Golang梦工厂"
switch demo.(type) {
case nil:
fmt.Printf("demo type is nil\n")
case int64:
fmt.Printf("demo type is int64\n")
case bool:
fmt.Printf("demo type is bool\n")
case string:
fmt.Printf("demo type is string\n")
default:
fmt.Printf("demo type unkonwn\n")
}
}
type switchgo.uber.org/zapzap.Any()casedefaultReflect
类型断言实现源码剖析
非空接口和空接口都可以使用类型断言,我们分两种进行剖析。
空接口
我们先来写一段测试代码:
type User struct {
Name string
}
func main() {
var u interface{} = &User{Name: "asong"}
val, ok := u.(int)
if !ok {
fmt.Printf("%v\n", val)
}
}
老样子,我们将上述代码转换成汇编代码看一下:
go tool compile -S -N -l main.go > main.s4 2>&1
截取部分重要汇编代码如下:
0x002f 00047 (main.go:12) XORPS X0, X0
0x0032 00050 (main.go:12) MOVUPS X0, ""..autotmp_8+136(SP)
0x003a 00058 (main.go:12) PCDATA $2, $1
0x003a 00058 (main.go:12) PCDATA $0, $0
0x003a 00058 (main.go:12) LEAQ ""..autotmp_8+136(SP), AX
0x0042 00066 (main.go:12) MOVQ AX, ""..autotmp_7+96(SP)
0x0047 00071 (main.go:12) TESTB AL, (AX)
0x0049 00073 (main.go:12) MOVQ $5, ""..autotmp_8+144(SP)
0x0055 00085 (main.go:12) PCDATA $2, $2
0x0055 00085 (main.go:12) LEAQ go.string."asong"(SB), CX
0x005c 00092 (main.go:12) PCDATA $2, $1
0x005c 00092 (main.go:12) MOVQ CX, ""..autotmp_8+136(SP)
0x0064 00100 (main.go:12) MOVQ AX, ""..autotmp_3+104(SP)
0x0069 00105 (main.go:12) PCDATA $2, $2
0x0069 00105 (main.go:12) PCDATA $0, $2
0x0069 00105 (main.go:12) LEAQ type.*"".User(SB), CX
0x0070 00112 (main.go:12) PCDATA $2, $1
0x0070 00112 (main.go:12) MOVQ CX, "".u+120(SP)
0x0075 00117 (main.go:12) PCDATA $2, $0
0x0075 00117 (main.go:12) MOVQ AX, "".u+128(SP)
interface{}efaceeface
_typedataeface_type+120(SP)unsafe.Pointer+128(SP)
0x007d 00125 (main.go:13) PCDATA $2, $1
0x007d 00125 (main.go:13) MOVQ "".u+128(SP), AX
0x0085 00133 (main.go:13) PCDATA $0, $0
0x0085 00133 (main.go:13) MOVQ "".u+120(SP), CX
0x008a 00138 (main.go:13) PCDATA $2, $3
0x008a 00138 (main.go:13) LEAQ type.int(SB), DX
0x0091 00145 (main.go:13) PCDATA $2, $1
0x0091 00145 (main.go:13) CMPQ CX, DX
0x0094 00148 (main.go:13) JEQ 155
0x0096 00150 (main.go:13) JMP 395
0x009b 00155 (main.go:13) PCDATA $2, $0
0x009b 00155 (main.go:13) MOVQ (AX), AX
0x009e 00158 (main.go:13) MOVL $1, CX
0x00a3 00163 (main.go:13) JMP 165
0x00a5 00165 (main.go:13) MOVQ AX, ""..autotmp_4+80(SP)
0x00aa 00170 (main.go:13) MOVB CL, ""..autotmp_5+71(SP)
0x00ae 00174 (main.go:13) MOVQ ""..autotmp_4+80(SP), AX
0x00b3 00179 (main.go:13) MOVQ AX, "".val+72(SP)
0x00b8 00184 (main.go:13) MOVBLZX ""..autotmp_5+71(SP), AX
0x00bd 00189 (main.go:13) MOVB AL, "".ok+70(SP)
0x00c1 00193 (main.go:14) CMPB "".ok+70(SP), $0
eface_typeval+72(SP)ok+70(SP)
0x018b 00395 (main.go:15) XORL AX, AX
0x018d 00397 (main.go:15) XORL CX, CX
0x018f 00399 (main.go:13) JMP 165
0x0194 00404 (main.go:13) NOP
AXCXAXCXeface
eface_type
非空接口
老样子,还是先写一个例子,然后我们在看他的汇编实现:
type Basic interface {
GetName() string
SetName(name string) error
}
type User struct {
Name string
}
func (u *User) GetName() string {
return u.Name
}
func (u *User) SetName(name string) error {
u.Name = name
return nil
}
func main() {
var u Basic = &User{Name: "asong"}
switch u.(type) {
case *User:
u1 := u.(*User)
fmt.Println(u1.Name)
default:
fmt.Println("failed to match")
}
}
使用汇编指令看一下他的汇编代码如下:
0x002f 00047 (main.go:26) PCDATA $2, $0
0x002f 00047 (main.go:26) PCDATA $0, $1
0x002f 00047 (main.go:26) XORPS X0, X0
0x0032 00050 (main.go:26) MOVUPS X0, ""..autotmp_5+152(SP)
0x003a 00058 (main.go:26) PCDATA $2, $1
0x003a 00058 (main.go:26) PCDATA $0, $0
0x003a 00058 (main.go:26) LEAQ ""..autotmp_5+152(SP), AX
0x0042 00066 (main.go:26) MOVQ AX, ""..autotmp_4+64(SP)
0x0047 00071 (main.go:26) TESTB AL, (AX)
0x0049 00073 (main.go:26) MOVQ $5, ""..autotmp_5+160(SP)
0x0055 00085 (main.go:26) PCDATA $2, $2
0x0055 00085 (main.go:26) LEAQ go.string."asong"(SB), CX
0x005c 00092 (main.go:26) PCDATA $2, $1
0x005c 00092 (main.go:26) MOVQ CX, ""..autotmp_5+152(SP)
0x0064 00100 (main.go:26) MOVQ AX, ""..autotmp_2+72(SP)
0x0069 00105 (main.go:26) PCDATA $2, $2
0x0069 00105 (main.go:26) PCDATA $0, $2
0x0069 00105 (main.go:26) LEAQ go.itab.*"".User,"".Basic(SB), CX
0x0070 00112 (main.go:26) PCDATA $2, $1
0x0070 00112 (main.go:26) MOVQ CX, "".u+104(SP)
0x0075 00117 (main.go:26) PCDATA $2, $0
0x0075 00117 (main.go:26) MOVQ AX, "".u+112(SP)
ifaceiface
0x00df 00223 (main.go:29) PCDATA $2, $1
0x00df 00223 (main.go:29) PCDATA $0, $2
0x00df 00223 (main.go:29) MOVQ "".u+112(SP), AX
0x00e4 00228 (main.go:29) PCDATA $0, $0
0x00e4 00228 (main.go:29) MOVQ "".u+104(SP), CX
0x00e9 00233 (main.go:29) PCDATA $2, $3
0x00e9 00233 (main.go:29) LEAQ go.itab.*"".User,"".Basic(SB), DX
0x00f0 00240 (main.go:29) PCDATA $2, $1
0x00f0 00240 (main.go:29) CMPQ CX, DX
0x00f3 00243 (main.go:29) JEQ 250
0x00f5 00245 (main.go:29) JMP 583
0x00fa 00250 (main.go:29) MOVQ AX, "".u1+56(SP)
ifaceitabifaceruntime/iface.go iface *itab +104(SP) unsafe.Pointer+112(SP)*itab*itab+104(SP) *itab
后面的赋值操作也就不再细说了,没有什么特别的。
这里还有一个要注意的问题,如果我们类型断言的是接口类型,那么我们在就会看到这样的汇编代码:
// 代码修改
func main() {
var u Basic = &User{Name: "asong"}
v, ok := u.(Basic)
if !ok {
fmt.Printf("%v\n", v)
}
}
// 部分汇编代码
0x008c 00140 (main.go:27) MOVUPS X0, ""..autotmp_4+168(SP)
0x0094 00148 (main.go:27) PCDATA $2, $1
0x0094 00148 (main.go:27) MOVQ "".u+128(SP), AX
0x009c 00156 (main.go:27) PCDATA $0, $0
0x009c 00156 (main.go:27) MOVQ "".u+120(SP), CX
0x00a1 00161 (main.go:27) PCDATA $2, $4
0x00a1 00161 (main.go:27) LEAQ type."".Basic(SB), DX
0x00a8 00168 (main.go:27) PCDATA $2, $1
0x00a8 00168 (main.go:27) MOVQ DX, (SP)
0x00ac 00172 (main.go:27) MOVQ CX, 8(SP)
0x00b1 00177 (main.go:27) PCDATA $2, $0
0x00b1 00177 (main.go:27) MOVQ AX, 16(SP)
0x00b6 00182 (main.go:27) CALL runtime.assertI2I2(SB)
runtime.assertI2I2()
func assertI2I(inter *interfacetype, i iface) (r iface) {
tab := i.tab
if tab == nil {
// explicit conversions require non-nil interface value.
panic(&TypeAssertionError{nil, nil, &inter.typ, ""})
}
if tab.inter == inter {
r.tab = tab
r.data = i.data
return
}
r.tab = getitab(inter, tab._type, false)
r.data = i.data
return
}
iface itab.inter*interfacetypeiface true iface itab.inter*interfacetype*interfacetypeiface.tab tab itabTableinterface getitab() canfail true*itab nil
ifaceruntime/iface.go
*itab*itab
类型断言的性能损耗
前面我们已经分析了断言的底层原理,下面我们来看一下不同场景下进行断言的代价。
针对不同的场景可以写出测试文件如下(截取了部分代码,全部代码获取戳这里):
var dst int64
// 空接口类型直接类型断言具体的类型
func Benchmark_efaceToType(b *testing.B) {
b.Run("efaceToType", func(b *testing.B) {
var ebread interface{} = int64(666)
for i := 0; i < b.N; i++ {
dst = ebread.(int64)
}
})
}
// 空接口类型使用TypeSwitch 只有部分类型
func Benchmark_efaceWithSwitchOnlyIntType(b *testing.B) {
b.Run("efaceWithSwitchOnlyIntType", func(b *testing.B) {
var ebread interface{} = 666
for i := 0; i < b.N; i++ {
OnlyInt(ebread)
}
})
}
// 空接口类型使用TypeSwitch 所有类型
func Benchmark_efaceWithSwitchAllType(b *testing.B) {
b.Run("efaceWithSwitchAllType", func(b *testing.B) {
var ebread interface{} = 666
for i := 0; i < b.N; i++ {
Any(ebread)
}
})
}
//直接使用类型转换
func Benchmark_TypeConversion(b *testing.B) {
b.Run("typeConversion", func(b *testing.B) {
var ebread int32 = 666
for i := 0; i < b.N; i++ {
dst = int64(ebread)
}
})
}
// 非空接口类型判断一个类型是否实现了该接口 两个方法
func Benchmark_ifaceToType(b *testing.B) {
b.Run("ifaceToType", func(b *testing.B) {
var iface Basic = &User{}
for i := 0; i < b.N; i++ {
iface.GetName()
iface.SetName("1")
}
})
}
// 非空接口类型判断一个类型是否实现了该接口 12个方法
func Benchmark_ifaceToTypeWithMoreMethod(b *testing.B) {
b.Run("ifaceToTypeWithMoreMethod", func(b *testing.B) {
var iface MoreMethod = &More{}
for i := 0; i < b.N; i++ {
iface.Get()
iface.Set()
iface.One()
iface.Two()
iface.Three()
iface.Four()
iface.Five()
iface.Six()
iface.Seven()
iface.Eight()
iface.Nine()
iface.Ten()
}
})
}
// 直接调用方法
func Benchmark_DirectlyUseMethod(b *testing.B) {
b.Run("directlyUseMethod", func(b *testing.B) {
m := &More{
Name: "asong",
}
m.Get()
})
}
运行结果:
goos: darwin
goarch: amd64
pkg: asong.cloud/Golang_Dream/code_demo/assert_test
Benchmark_efaceToType/efaceToType-16 1000000000 0.507 ns/op
Benchmark_efaceWithSwitchOnlyIntType/efaceWithSwitchOnlyIntType-16 384958000 3.00 ns/op
Benchmark_efaceWithSwitchAllType/efaceWithSwitchAllType-16 351172759 3.33 ns/op
Benchmark_TypeConversion/typeConversion-16 1000000000 0.473 ns/op
Benchmark_ifaceToType/ifaceToType-16 355683139 3.38 ns/op
Benchmark_ifaceToTypeWithMoreMethod/ifaceToTypeWithMoreMethod-16 85421563 12.8 ns/op
Benchmark_DirectlyUseMethod/directlyUseMethod-16 1000000000 0.000000 ns/op
PASS
ok asong.cloud/Golang_Dream/code_demo/assert_test 7.797s
从结果我们可以分析一下:
type switchcase
好啦,现在我们也知道怎样使用类型断言能提高性能啦,又可以和同事吹水一手啦。
总结
好啦,本文到这里就已经接近尾声了,在最后做一个小小的总结:
eface_type*itab*itab
githubstar
好啦,这篇文章就到这里啦,素质三连(分享、点赞、在看)都是笔者持续创作更多优质内容的动力!
创建了一个Golang学习交流群,欢迎各位大佬们踊跃入群,我们一起学习交流。入群方式:加我vx拉你入群,或者公众号获取入群二维码
结尾给大家发一个小福利吧,最近我在看[微服务架构设计模式]这一本书,讲的很好,自己也收集了一本PDF,有需要的小伙可以到自行下载。获取方式:关注公众号:[Golang梦工厂],后台回复:[微服务],即可获取。
我翻译了一份GIN中文文档,会定期进行维护,有需要的小伙伴后台回复[gin]即可下载。
翻译了一份Machinery中文文档,会定期进行维护,有需要的小伙伴们后台回复[machinery]即可获取。
我是asong,一名普普通通的程序猿,让我们一起慢慢变强吧。欢迎各位的关注,我们下期见~~~
推荐往期文章: