煤矸石空心砖

新闻分类

联系我们Contact

企业名称:桐城市南口新型建材有限公司

联系人:崔经理

电话:0556-6568069

手机:18156911555

邮箱:303927413@qq.com

地址:桐城市龙腾街道高桥村

网址:   www.nkxxjc.com 



您的当前位置: 首 页 > Zoctopus > Go语言之闭包篇

Go语言之闭包篇

发布日期:2023-01-18 02:52 作者: 点击:

在有GC和闭包实现的语言中,我最熟悉的是Lua语言。所以在使用Go语言时,碰到不熟悉的细节,总是会以Lua的机制来对比。

然而由于动态语言和静态语言的区别(静态语言总是有更多优化的机制), 以至于很多时候会得出错误的结论。

比如下面代码:

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

这段代码定义了一个闭包,然后作为参数传给exist函数。

按照Lua的经验,定义闭包肯定是需要malloc内存。然而Go语言反手就教我做人。

使用go run -gcflags="-m -l" a.go可以发现,这个闭包并没有被分配在堆上。

再使用go tool compile -N -l -S a.go来看一下与闭包相关的Plan9汇编代码。

"".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

上面的代码并不算太复杂,我们大致可以翻译出他的等价Go语言(翻译出来的代码是可以被编译运行的)。

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 f.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) } c := &Closure1{ F: func1, n: count, } exist(a, c) }

从上面的Go代码可以很清楚的看到,其实一个闭包到底分配不分配内存,关键就在于Closure1在栈上还是在堆上。

当Closure1结构暴露出来之后,一切都是那么的显然。

即然闭包是一个struct对象,那么Go当然可以和一般的自定义struct一样进行逃逸分析,而根据逃逸规则,这里的c对象显然不需要逃逸。

一切都很完美,只是还有一个问题没有解决。

exist在调用f函数时,是如何区分调用的是闭包还是非闭包,比如下面代码:

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, foo) }

再来看一下对应汇编代码:

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

再来将汇编翻译成Go语言:

package main import "os" type Closure1 struct { F func(int) bool } var DX *Closure1 func foo(n int) bool { return n == 3 } func exist(list []int, f *Closure1) bool { for _, n := range list { DX = f if f.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) } c := &Closure1{ F: foo, } exist(a, c) }

通过对比两次翻译后的Go语言,可以发现一件很有意思的事。

Go语言其实把所有函数都抽象成闭包,这一点倒是与Lua有颇多相似之处。

只是没有任何值捕获的闭包,在逃逸分析时可以做更多的优化。

本文网址:

关键词:Zoctopus

相关新闻: