前言

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,一名普普通通的程序猿,让我们一起慢慢变强吧。欢迎各位的关注,我们下期见~~~

推荐往期文章: