In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-02-25 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/03 Report--
This article mainly introduces "how to use Go defer correctly". In daily operation, I believe many people have doubts about how to use Go defer correctly. The editor consulted all kinds of materials and sorted out simple and easy-to-use operation methods. I hope it will be helpful to answer the doubts about "how to use Go defer correctly". Next, please follow the editor to study!
Defer is an interesting keyword feature in the Go language. Examples are as follows:
Package main import "fmt" func main () {defer fmt.Println ("fried fish") fmt.Println ("brain")}
The output is as follows:
Fried fish in my head.
A few days ago, some friends in my readership discussed the following question:
Chat screenshot of the reader group
In a nutshell, the question is whether there will be any performance impact on the defer keyword in the for loop.
Because in the underlying data structure design of the Go language, defer is the data structure of linked lists:
Defer basic underlying structure
We are worried that if the loop is too large, the defer chain list will be too long, not enough "Excelsior". Or wonder if the design of Go defer is similar to the design of Redis data structure, and you have optimized it yourself, but it doesn't matter much.
In today's article, we will explore whether circular Go defer will cause any problems if the underlying linked list is too long, and if so, what is the specific impact?
Start to suck the fish.
Defer performance optimization 30%
In the early years of Go1.13, there was a round of performance optimization for defer, which improved the performance of defer by 30% in most scenarios:
Go defer 1.13 optimized recording
Let's review the changes to Go1.13 and see where the Go defer optimization is, which is the crux of the problem.
Compare the past with the present
In Go1.12 and before, the assembly code for calling Go defer is as follows:
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
In Go1.13 and beyond, the assembly code for calling Go defer is as follows:
0x006e 00110 (main.go:4) MOVQ AX, (SP) 0x0072 00114 (main.go:4) CALL runtime.deferprocStack (SB) 0x0077 00119 (main.go:4) TESTL AX, AX 0x0079 00121 (main.go:4) JNE 139 0x007b 00123 (main.go:7) XCHGL AX, AX 0x007c 00124 (main.go:7) CALL runtime.deferreturn (SB) 0x0081 00129 (main.go:7) MOVQ 112 (SP), BP
From an assembly point of view, it seems that the original call to the runtime.deferproc method has been changed to the call to the runtime.deferprocStack method, is it something optimized?
We continued to watch with doubt.
Defer minimum unit: _ defer
Compared with the previous version, the minimum unit _ defer structure of Go defer mainly adds a heap field:
Type _ defer struct {siz int32 siz int32 / / includes both arguments and results started bool heap bool sp uintptr / / sp at time of defer pc uintptr fn * funcval...
This field is used to identify whether the _ defer is allocated on the heap or on the stack, and the rest of the fields have not been explicitly changed, so we can focus on the stack allocation of defer and see what has been done.
DeferprocStack
Func deferprocStack (d * _ defer) {gp: = getg () if gp.m.curg! = gp {throw ("defer on system stack")} d.started = false d.heap = false d.sp = getcallersp () d.pc = getcallerpc () * (* uintptr) (unsafe.Pointer (& d._panic)) = 0 * (* uintptr) (unsafe.Pointer (& d.link) = uintptr (unsafe.Pointer (gp._defer)) * (* uintptr) (unsafe.Pointer (& gp._defer)) = uintptr (unsafe.Pointer (d)) return0 ()}
This piece of code is quite conventional, mainly to get the function stack pointer that calls the defer function, the specific address of the parameters of the incoming function, and PC (program counter). This piece has been described in detail in the previous article, "in-depth understanding of Go defer," so I won't repeat it here.
What's so special about this deferprocStack?
You can see that it sets d.heap to false, which means that the deferprocStack method is for the application scenario where _ defer is assigned on the stack.
Deferproc
The question is, where does it deal with application scenarios assigned to the heap?
Func newdefer (siz int32) * _ defer {... D.heap = true d.link = gp._defer gp._defer = d return d}
Where is the specific newdefer called, as follows:
Func deferproc (siz int32, fn * funcval) {/ / arguments of fn follow fn. Sp: = getcallersp () argp: = uintptr (unsafe.Pointer (& fn)) + unsafe.Sizeof (fn) callerpc: = getcallerpc () d: = newdefer (siz)...}
It is clear that the deferproc method called in the previous version is now used to correspond to the scenario assigned to the heap.
Summary
What is certain is that the deferproc is not removed, but the process is optimized.
The Go compiler chooses to use deferproc or deferprocStack methods based on the application scenario, which is for usage scenarios allocated on the heap and stack, respectively.
Where is the optimization?
The main optimization lies in the change of the stack allocation rules of its defer objects. The measure is that the compiler analyzes the for-loop iteration depth of defer.
/ / src/cmd/compile/internal/gc/esc.go case ODEFER: if e.loopdepth = = 1 {/ / top level n.Esc = EscNever / / force stack allocation of defer record (see ssa.go) break}
If the Go compiler detects that the loop depth (loopdepth) is 1, the result of the escape analysis is set and assigned to the stack, otherwise to the heap.
/ / src/cmd/compile/internal/gc/ssa.go case ODEFER: d: = callDefer if n.Esc = = EscNever {d = callDeferStack} s.call (n.Left, d)
This avoids a lot of performance overhead caused by frequent calls to systemstack, mallocgc and other methods in the past, so as to improve the performance of most scenarios.
Call defer in a loop
Going back to the problem itself, after knowing the principle of defer optimization. Then "will there be any performance impact if you use the defer keyword in the loop?"
The most direct impact is that about 30% of the performance optimization is not at all, and because of incorrect posture, the existing overhead of defer (longer linked list) becomes larger and the performance becomes worse.
So we need to avoid the code for the following two scenarios:
Explicit loop: there is an explicit loop call outside the calling defer keyword, such as a for-loop statement, etc.
Implicit loop: there is similar loop nesting logic when calling the defer keyword, such as goto statements, etc.
Explicit loop
The first example is to use the defer keyword directly in the for loop of the code:
Func main () {for I: = 0; I
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.