前言

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)TtoktrueTpanicokfalsetT
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.goiface*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
}
ifaceitab.inter*interfacetypeifacetrueifaceitab.inter*interfacetype*interfacetypeiface.tabtabitabTableinterfacegetitab()canfailtrue*itabnil
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
github