Go defer will have performance loss, so try not to use it?

  defer, golang, php

image

Original address:Go defer will have performance loss, so try not to use it?

Last month, I saw a little friend ask in the full stack technology group of @polaris @ Xuan Mai Blade.“Said defer when the stack exits, there will be performance loss, try not to use, this how to solve?”.

I just wrote an article some time ago.“understanding Go defer in depth”To analyze in detaildeferKey words. This time, I hope it will be helpful for you to have a discussion on this issue, but before that, I hope you can spend a few minutes, think about the answer yourself, and then continue to look down.

test

func DoDefer(key, value string) {
    defer func(key, value string) {
        _ = key + value
    }(key, value)
}

func DoNotDefer(key, value string) {
    _ = key + value
}

Benchmarking:

func BenchmarkDoDefer(b *testing.B) {
    for i := 0; i < b.N; i++ {
        DoDefer("煎鱼", "https://github.com/EDDYCJY/blog")
    }
}

func BenchmarkDoNotDefer(b *testing.B) {
    for i := 0; i < b.N; i++ {
        DoNotDefer("煎鱼", "https://github.com/EDDYCJY/blog")
    }
}

Output results:

$ go test -bench=. -benchmem -run=none
goos: darwin
goarch: amd64
pkg: github.com/EDDYCJY/awesomeDefer
BenchmarkDoDefer-4          20000000            91.4 ns/op          48 B/op           1 allocs/op
BenchmarkDoNotDefer-4       30000000            41.6 ns/op          48 B/op           1 allocs/op
PASS
ok      github.com/EDDYCJY/awesomeDefer    3.234s

From the results, usedeferAfter that, the cost of the function is much higher than that of not using it. Where is this loss going?

Think about it.

$ go tool compile -S main.go 
"".main STEXT size=163 args=0x0 locals=0x40
    ...
    0x0059 00089 (main.go:6)    MOVQ    AX, 16(SP)
    0x005e 00094 (main.go:6)    MOVQ    $1, 24(SP)
    0x0067 00103 (main.go:6)    MOVQ    $1, 32(SP)
    0x0070 00112 (main.go:6)    CALL    runtime.deferproc(SB)
    0x0075 00117 (main.go:6)    TESTL    AX, AX
    0x0077 00119 (main.go:6)    JNE    137
    0x0079 00121 (main.go:7)    XCHGL    AX, AX
    0x007a 00122 (main.go:7)    CALL    runtime.deferreturn(SB)
    0x007f 00127 (main.go:7)    MOVQ    56(SP), BP
    0x0084 00132 (main.go:7)    ADDQ    $64, SP
    0x0088 00136 (main.go:7)    RET
    0x0089 00137 (main.go:6)    XCHGL    AX, AX
    0x008a 00138 (main.go:6)    CALL    runtime.deferreturn(SB)
    0x008f 00143 (main.go:6)    MOVQ    56(SP), BP
    0x0094 00148 (main.go:6)    ADDQ    $64, SP
    0x0098 00152 (main.go:6)    RET
    ...

We mentioned earlierdeferKeyword actually involves a series of chain calls, internallyruntimeThere are at least three more steps to call the function, namelyruntime.deferprocOnce and for allruntime.deferreturnTwice.

However, this is only an explicit action at runtime. In addition, the compiler does a lot of things, such as:

  • IndeferprocPhase (registration of delayed calls) requires obtaining/passing in the target function address, function parameters, etc.
  • IndeferreturnPhase, you need to insert the call of this method at the end of the function call, and if it isdeferThe function of, also need to useruntime·jmpdeferJump for subsequent calls.

These actions also involve the smallest unit on the way._deferThe acquisition/generation of,deferAndrecoverLogical processing and consumption of linked lists.

Q&A

It was mentioned in the final discussion.“The problem is that it is originally used to perform some operations of close (), and then it is said that it cannot be used as much as possible. For example, the defer in front of defer db.close () is deleted.”This question.

This is a relatively similar “textbook” statement, which will subtly tell you to add one after resource control in some introductory courses.deferDelay closing. For example:

resp, err := http.Get(...)
if err != nil {
    return err
}
defer resp.Body.Close()

But does it have to be written like this? In fact, it is not. Many people give the excuse of “fearing you forget”. There is nothing wrong with it. However, it is necessary to recognize the scenario and assume that my application scenario is as follows:

resp, err := http.Get(...)
if err != nil {
    return err
}
defer resp.Body.Close()
// do something
time.Sleep(time.Second * 60)

Well, of course, a request is no problem. The traffic and concurrency have suddenly increased. It may be a disaster. Do you think why? From the commondefer+closeIn terms of usage combination, it is recommended to look at the application scenario clearly before using, and it is the first choice to ensure early shutdown under the condition of no abnormality. If it is only a small-scale call and returns soon, it is not impossible to steal a lazy and direct combination boxing.

Conclusion

OnedeferKeyword actually contains a lot of actions and processing, and you simply call a function an instruction is not comparable. Compared with the control, it does have performance loss. At present, the total cost of delayed call is about 50ns, butdeferThe function provided is far greater than this. From a global point of view, its loss is very small and the government is constantly optimizing it.

Therefore, for “Go defer will have performance loss and cannot be used as much as possible?” This problem, I thinkUse it when you need it, close it in time without delay, and think clearly about the scene when using hot paths..

supplement

Supplementary reply from Shangchai University:“It’s not a performance issue, defer’s biggest function is to remain effective after Panic. If there is no defer, Panic will cause unlock to be lost, resulting in deadlock. “, very classic.