Among the languages with GC and closure implementation, I am most familiar with Lua language. Therefore, when using the Go language, when encountering unfamiliar details, it will always be compared with the Lua mechanism.
However, due to the difference between dynamic language and static language (static language always has more optimization mechanisms), so many times wrong conclusions will be drawn.
For example the following code:
package main import "os" func exist(list []int, f func(n int)bool) bool { for _, n := range list { if f(n) == true { return true } } return false } func main() { count := len(os.Args) a := make([]int, 0) for i := 0; i < count; i++ { a = append(a, i) } exist(a, func(n int) bool { return n == (count - 1) }) }
This code defines a closure, which is then passed as a parameter to the exist function.
According to Lua’s experience, defining closures definitely requires malloc memory. However, the Go language taught me how to be a human being.
Use go run -gcflags="-m -l" a.go
to find that this closure has not been allocated on the heap.
Then use go tool compile -N -l -S a.go
to look at the Plan9 assembly code related to the closure.
"".exist STEXT size=234 args=0x28 locals=0x58 ................ 0x0085 00133 (a.go:5) MOVQ "".f+120(SP), DX 0x008a 00138 (a.go:5) MOVQ AX, (SP) 0x008e 00142 (a.go:5) MOVQ (DX), AX 0x0091 00145 (a.go:5) PCDATA $1, $1 0x0091 00145 (a.go:5) CALL AX 0x0093 00147 (a.go:5) MOVBLZX 8(SP), AX 0x0098 00152 (a.go:5) MOVB AL, ""..autotmp_5+23(SP) 0x009c 00156 (a.go:5) NOP 0x00a0 00160 (a.go:5) TESTB AL, AL 0x00a2 00162 (a.go:5) JNE 166 0x00a4 00164 (a.go:5) JMP 184 0x00a6 00166 (a.go:6) MOVB $1, "".~r2+128(SP) 0x00ae 00174 (a.go:6) MOVQ 80(SP), BP 0x00b3 00179 (a.go:6) ADDQ $88, SP 0x00b7 00183 (a.go:6) RET 0x00b8 00184 (a.go:5) PCDATA $1, $-1 0x00b8 00184 (a.go:5) JMP 186 0x00ba 00186 (a.go:5) JMP 188 "".main STEXT size=372 args=0x0 locals=0x90 ................ 0x00ff 00255 (a.go:17) XORPS X0, X0 0x0102 00258 (a.go:17) MOVUPS X0, ""..autotmp_4+88(SP) 0x0107 00263 (a.go:17) LEAQ ""..autotmp_4+88(SP), AX 0x010c 00268 (a.go:17) MOVQ AX, ""..autotmp_6+104(SP) 0x0111 00273 (a.go:17) TESTB AL, (AX) 0x0113 00275 (a.go:17) LEAQ "".main.func1(SB), CX 0x011a 00282 (a.go:17) MOVQ CX, ""..autotmp_4+88(SP) 0x011f 00287 (a.go:17) TESTB AL, (AX) 0x0121 00289 (a.go:17) MOVQ "".count+72(SP), AX 0x0126 00294 (a.go:17) MOVQ AX, ""..autotmp_4+96(SP) 0x012b 00299 (a.go:17) MOVQ "".a+112(SP), AX 0x0130 00304 (a.go:17) MOVQ "".a+120(SP), CX 0x0135 00309 (a.go:17) MOVQ "".a+128(SP), DX 0x013d 00317 (a.go:17) MOVQ AX, (SP) 0x0141 00321 (a.go:17) MOVQ CX, 8(SP) 0x0146 00326 (a.go:17) MOVQ DX, 16(SP) 0x014b 00331 (a.go:17) MOVQ ""..autotmp_6+104(SP), AX 0x0150 00336 (a.go:17) MOVQ AX, 24(SP) 0x0155 00341 (a.go:17) CALL "".exist(SB) "".main.func1 STEXT nosplit size=54 args=0x10 locals=0x10 0x0000 00000 (a.go:17) TEXT "".main.func1(SB), NOSPLIT|NEEDCTXT|ABIInternal, $16-16 0x0000 00000 (a.go:17) SUBQ $16, SP 0x0004 00004 (a.go:17) MOVQ BP, 8(SP) 0x0009 00009 (a.go:17) LEAQ 8(SP), BP 0x000e 00014 (a.go:17) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x000e 00014 (a.go:17) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x000e 00014 (a.go:17) MOVQ 8(DX), AX 0x0012 00018 (a.go:17) MOVQ AX, "".count(SP) 0x0016 00022 (a.go:17) MOVB $0, "".~r1+32(SP) 0x001b 00027 (a.go:18) MOVQ "".count(SP), AX 0x001f 00031 (a.go:18) DECQ AX 0x0022 00034 (a.go:18) CMPQ "".n+24(SP), AX 0x0027 00039 (a.go:18) SETEQ "".~r1+32(SP) 0x002c 00044 (a.go:18) MOVQ 8(SP), BP 0x0031 00049 (a.go:18) ADDQ $16, SP 0x0035 00053 (a.go:18) RET
The above code is not too complicated, we can roughly translate his equivalent Go language (this code can be compiled and run).
package main import "os" type Closure1 struct { F func(int) bool n int } var DX *Closure1 func func1(n int) bool { x := DX.n - 1 return x == n } func exist(list []int, f *Closure1) bool { for _, n := range list { DX = f if fF(n) == true { return true } } return false } func main() { count := len(os.Args) a := make([]int, 0) for i := 0; i < count; i++ { a = append(a, i) } c := &Closure1{ F: func1, n: count, } exist(a, c) }
From the above Go code, it can be clearly seen that whether a closure allocates memory or not depends on whether Closure1 is on the stack or on the heap.
When the Closure1 structure is exposed, everything is so obvious.
Since the closure is a struct object, Go can of course perform escape analysis like a general custom struct, and according to the escape rules, c
object here obviously does not need to escape.
Everything is perfect, there is only one problem that has not been solved.
When exist
calls the f function, how does it distinguish whether the call is a closure or a non-closure , such as the following code:
package main import "os" func exist(list []int, f func(n int) bool) bool { for _, n := range list { if f(n) == true { return true } } return false } func foo(n int) bool { return n == 3 } func main() { count := len(os.Args) a := make([]int, 0) for i := 0; i < count; i++ { a = append(a, i) } exist(a, func (n int) bool { return n == x }) }
Let’s take a look at the assembly again:
"".exist STEXT size=234 args=0x28 locals=0x58 ....... 0x0085 00133 (a.go:5) MOVQ "".f+120(SP), DX 0x008a 00138 (a.go:5) MOVQ AX, (SP) 0x008e 00142 (a.go:5) MOVQ (DX), AX 0x0091 00145 (a.go:5) PCDATA $1, $1 0x0091 00145 (a.go:5) CALL AX 0x0093 00147 (a.go:5) MOVBLZX 8(SP), AX 0x0098 00152 (a.go:5) MOVB AL, ""..autotmp_5+23(SP) 0x009c 00156 (a.go:5) NOP 0x00a0 00160 (a.go:5) TESTB AL, AL 0x00a2 00162 (a.go:5) JNE 166 0x00a4 00164 (a.go:5) JMP 184 ....... "".main STEXT size=300 args=0x0 locals=0x78 0x00ea 00234 (a.go:20) MOVQ "".a+88(SP), AX 0x00ef 00239 (a.go:20) MOVQ "".a+96(SP), CX 0x00f4 00244 (a.go:20) MOVQ "".a+104(SP), DX 0x00f9 00249 (a.go:20) MOVQ AX, (SP) 0x00fd 00253 (a.go:20) MOVQ CX, 8(SP) 0x0102 00258 (a.go:20) MOVQ DX, 16(SP) 0x0107 00263 (a.go:20) LEAQ "".foo·f(SB), AX 0x010e 00270 (a.go:20) MOVQ AX, 24(SP) 0x0113 00275 (a.go:20) CALL "".exist(SB)
Through comparison, it can be found that the code of the exist function has not changed at all, and the changed code is line 20 of a.go
Let’s translate the assembly into Go language for comparison:
package main import "os" type Closure1 struct { F func(int) bool } var DX *Closure1 func func1(n int) bool { return x == 3 } func exist(list []int, f *Closure1) bool { for _, n := range list { DX = f if fF(n) == true { return true } } return false } func main() { count := len(os.Args) a := make([]int, 0) for i := 0; i < count; i++ { a = append(a, i) } c := &Closure1{ F: func1, } exist(a, c) }
Comparing the Go language after two translations , it can be found.
Go language actually abstracts all functions into closures , but functions without any value capture can be optimized more during escape analysis, that’s all. There are many similarities with Lua in this regard.
The post Go Language Closures first appeared on Return to Chaos BLOG .
This article is transferred from: https://blog.gotocoding.com/archives/1786?utm_source=rss&utm_medium=rss&utm_campaign=go%25e8%25af%25ad%25e8%25a8%2580%25e4%25b9%258b%25e9%2597%25ad%25e5 %258c%2585%25e7%25af%2587
This site is only for collection, and the copyright belongs to the original author.