preface

Hello, everyBody
asongToday we’re going to explore
interfaceHow type assertions are implemented for. We usually use
interfaceThere are two ways. One is with methods
interfaceOne is empty
interface. because
GoThere are no generics in, so we can use empty
interface{}To be used as a pseudo-generic when we use an empty
interface{}As an input or return value, type assertions are used to get the type we want, so we see a lot of use of type assertions in our code. Have you ever wondered how it works? Aren’t you curious what the performance cost is? Anyway, I’m curious.

Basic use of type assertions

Assertion Type Assertion is an operation used for interface value with the syntax x.(T), where x is an expression of interface Type and T is asserted Type. Assertion Type Here’s an example of basic usage:

Func main() {var demo interface{} = "Golang "STR := demo.(string) fmt.printf ("value: %v", STR)}

We declare an interface object demo. We assert that demo is nil by means of type assertion, and determine that the value stored by the interface object demo is of type T. If the assertion succeeds, we will return the value to STR, if the assertion fails, we will trigger panic. This code will trigger panic if written like this:

Number := demo.(int64) fmt.Printf("value: %v\n", number)

So to be on the safe side, we can also use:

Func main() {var demo interface{} = number, ok := demo.(int64) if! Ok {fmt.Printf("assert failed") return} fmt.Printf("value: %v\n", number)} Assert failed

The expression used here is t,ok:= I.(t). This expression can also assert that an interface object (I) is not nil and that the value stored by the interface object (I) is of type T. If the assertion succeeds, it will return its type to t, and the value of OK is true, indicating that the assertion succeeded. If the interface value is of a type other than T, the assertion will fail. Unlike the first expression, this does not trigger panic. Instead, the assertion fails by setting ok to false, where T is zero for T. Therefore, it is recommended to use this method to ensure the robustness of the code.

If we want to distinguish between multiple types, we can use the type switch assertion, which is simpler and more efficient than having to assert each type one by one. We can change the above code to look like this:

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") } }

A typical application of type switch is in the go.uber.org/zap library’s zap.any () method, which uses type assertions to list all types of cases. The default branch uses Reflect. That is, when all types do not match, use reflection to get the corresponding value, as you can see in the source code.

Type assertion implements source profiling

Type assertions can be used on both non-empty and empty interfaces, and we’ll analyze them in two ways.

Empty interface

Let’s write some test code:

type User struct { Name string } func main() { var u interface{} = &User{Name: "asong"} val, ok := u.(int) if ! ok { fmt.Printf("%v\n", val) } }

As usual, let’s convert the above code into assembly code:

go tool compile -S -N -l main.go > main.s4 2>&1

Some important assembly code is captured as follows:

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)

{} = eface;} = eface;} = eface;

Unsafe.pointer is stored in memory at +120(SP), while the unsafe.pointer exists at +128 (SP). Now that we know how it works, let’s take a look at how the null interface type assertion assembly is implemented:

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

As we can see from the above assembly, the null interface type assertion is made by comparing the type of the eface field with the type of the comparison. The same is used to prepare the next return value. If the type assertion is correct, the intermediate temporary variable is passed and val is stored in memory at +72(SP). Ok is saved in memory +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

If the assertion fails, the AX and CX registers are emptied because AX and CX hold the fields in the Eface structure.

Finally, to summarize the implementation process of null interface type assertion: the essence of null interface type assertion is toefaceIn the_typeIf the match succeeds, the return value is assembled in memory. If the match fails, the register is emptied and the default value is returned.

Not empty interface

As usual, let’s write an example first, and then we’ll look at its assembly implementation:

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")
    }
}

Using assembly instructions, look at his assembly code as follows:

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)

The iFace structure is assigned to a non-null interface, and the memory layout of the iface is assembled. The iFace structure is assigned to a non-null interface, and the memory layout is assembled. Let’s look at how he makes type assertions.

    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)

In the above code we can see that the itAB field in the iface structure is called. Why is it called here? Because we type infer a specific type, the compiler constructs iFace directly and doesn’t call the good assertion methods already implemented in Runtime/iface-go. The above code, the first constructs the iface, including * itab exist memory + 104 (SP), the unsafe. The Pointer exist + 112 (SP). Then the *itab was reconstructed again during type inference, and the new *itab was compared to the previous *itab in +104(SP).

I won’t go into the details of the assignment, there’s nothing special about it.

One more thing to note here is that if we were asserting an interface type, we would see assembly code like this:

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)

As you can see, the runtime. AssertI2I2 () method is called directly to assert the type. The implementation code for this method is as follows:

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
}

If itab.inter and interfaceType are the same, then the type is the same. If itab.inter and interfaceType are the same, then the type is the same. If itab.inter and interfaceType in iface are not the same, then the TAB is constructed from * interfaceType and iface. TAB. The construction process looks for itabTable. If the types do not match, or do not belong to the same interface type, it will fail. The third argument of the getitab() method is canfail, which is passed true to indicate that the build * is allowed to fail and itab returns nil on failure.

Difference: If the type we assert is a concrete type, the compiler constructs iFace directly instead of calling the good assertion methods already implemented in Runtime /iface.go. If the type we assert is an interface type, the corresponding assert method will be called to determine.

Summary: The essence of non-empty interface type assertions is a comparison of * ITAB in iface. Itab will assemble the return value in memory for a successful match. If the match fails, the register is emptied and the default value is returned.

The performance cost of type assertions

Now that we’ve analyzed the underlying principles of assertions, let’s look at the costs of assertions in different scenarios.

For different scenarios, you can write a test file as follows (part of the code is intercepted, and the whole code is stamped here) :

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)}})} / / air interface type using TypeSwitch only some types of func Benchmark_efaceWithSwitchOnlyIntType (b * testing. B) { b.Run("efaceWithSwitchOnlyIntType", func(b *testing.B) { var ebread interface{} = 666 for i := 0; i < b.N; Func Benchmark_efaceWithSwitchAllType(b *testing.B) { b.Run("efaceWithSwitchAllType", func(b *testing.B) { var ebread interface{} = 666 for i := 0; i < b.N; 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) {func Benchmark_ifaceToType(b *testing. b.Run("ifaceToType", func(b *testing.B) { var iface Basic = &User{} for i := 0; i < b.N; I++ {iface. GetName () iface. Elegantly-named SetName (" 1 ")}})} / / not empty interface type judge whether a type implements this interface method of 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() }) }

Running result:

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.797 s

From the results, we can analyze:

  • Type assertion for null interface types is not expensive and has little performance difference from direct type conversions
  • The interface type is emptytype switchWhen making type assertions, followcaseThe increase in performance will plummet
  • When type assertions are made for non-empty interface types, performance plummets as the number of methods in the interface increases
  • Direct method calls are much more efficient than type assertions for non-interface types

Well, now that we know how to use type assertions to improve performance, we’re ready to play ball with our colleagues.

conclusion

Well, this article is close to the end, in the last to make a small summary:

  • Implementation process of null interface type assertion: The essence of null interface type assertion is toefaceIn the_typeIf the match succeeds, the return value is assembled in memory. If the match fails, the register is emptied and the default value is returned.
  • The essence of non-empty interface type assertions is in iface*itabContrast.*itabA successful match assembles the return value in memory. If the match fails, the register is emptied and the default value is returned
  • Generics are something you do at compile time, and using type assertions is a bit of a performance cost. The performance cost varies depending on how type assertions are used, as described in the section above.

The code has been uploadedgithub:https://github.com/asong2020/…star

Well, that’s it for this post, and the three qualities (share, like, and read) are the motivation for me to keep creating more quality content!

Created a Golang learning exchange group, welcome you to join the group, we learn and communicate together. Group entry method: add my VX to pull you into the group, or the public number to obtain the group TWO-DIMENSIONAL code

At the end of a small welfare to everyone, I recently read [micro service architecture design patterns] this book, it is very good, I also collected a PDF, there is a need to the guy can download. Access to: follow the public number: [Golang Dreamworks], the background reply: [microservices], you can get.

I have translated a Chinese GIN document, which will be maintained regularly. If you need a background reply [GIN], you can download it.

Translated a Chinese document of Machinery, will be regularly maintained, there is a need for friends to reply [Machinery] background can be obtained.

I am ASong, an ordinary program ape, let us slowly become strong together. We’ll see you next time ~~~

Recommended previous articles:

  • The Go source code must know the Unsafe package
  • Source code analysis panic and recover, do not understand you hit me!
  • Atomic Operations in Parallel Programming (Atomic package)
  • Details the defer implementation mechanism
  • Large face slapping scene caused by empty structure
  • Leaf — Segment Distributed ID Generation System (Golang implementation version)
  • 10 GIFs with you to understand the sorting algorithm (with go implementation code)
  • Go Parameter passing type
  • Hand taught my sister to write message queue
  • Often meet the exam cache avalanche, cache penetration, cache breakdown
  • Detailed Context package, read this article is enough!!
  • Interviewer: Can you write a piece of code in Go to determine the current system storage?
  • How do I smoothly switch online Elasticsearch indexes