pos机显示调试版本,1.14版本defer性能大幅度提升

 新闻资讯2  |   2023-06-12 09:08  |  投稿人:pos机之家

网上有很多关于pos机显示调试版本,1.14版本defer性能大幅度提升的知识,也有很多人为大家解答关于pos机显示调试版本的问题,今天pos机之家(www.poszjia.com)为大家整理了关于这方面的知识,让我们一起来看下吧!

本文目录一览:

1、pos机显示调试版本

pos机显示调试版本

你的留言,是我坚持分享的动力。如果你有任何疑问,可以点击关注,点赞、留言提问。

GO中的defer会在当前函数返回前执行传入的函数,常用于关闭文件描述符,关闭链接及解锁等操作。

Go语言中使用defer时会遇到两个常见问题:

defer的调用时机和执行顺序defer调用函数使用传值的方法会执行函数,导致不符预期的结果

接下来我们来详细处理这两个问题。

一、defer

官方有段对defer的解释:

每次defer语句执行时,会把函数“压栈”,函数的参数会被拷贝下来,当外层函数退出时,defer函数按照定义的逆序执行,如果defer执行的函数为nil,那么会在最终调用函数产生panic。

这里我们先来一道经典的面试题

func main() {a,b := 1,2defer cal("1",a,cal("10",a,b))a = 0defer cal("2",a,cal("20",a,b))}func cal(index string, a, b int) int {ret := a + bfmt.Println(index,a,b,ret)return ret}

你觉得这个会打印什么?

输出结果:

10 1 2 320 0 2 22 0 2 21 1 3 4

这里是遵循先入后出的原则,同时保留当前变量的值。

把这道题简化一下:

func main() {var i = 1defer fmt.Println(i)i = 4}

输出结果

1

上述代码输出似乎不符合预期,这个现象出现的原因是什么呢?经过分析,我们发现调用defer关键字会立即拷贝函数中引用的外部参数,所以fmt.Println(i)的这个i是在调用defer的时候就已经赋值了,所以会直接打印1。

想要解决这个问题也很简单,只需要向defer关键字传入匿名函数

func main() {var i = 1defer func() {fmt.Println(i)}()i = 4}二、数据结构

// 占用48字节的内存,link把所有的_defer串成一个链表。// _defer 只是一个header,结构紧跟的是延迟函数的参数和返回值的空间,大小由 siz 决定type _defer struct {// 参数和返回值的内存大小siz int32 started bool// 区分该结构是在栈上分配还是在堆上分配heap boolopenDefer bool// sp 计数器值,栈指针sp uintptr // sp at time of defer// pc 计数器值,程序计数器pc uintptr // pc at time of defer// defer 传入的函数地址,也就是延后执行的函数fn *funcval // can be nil for open-coded defers // 触发延迟调用的结构体_panic *_panic // 下一个要被执行的延迟函数link *_defer }

这里把一些垃圾回收使用的字段忽略了。

2.1、执行机制

中间代码生成阶段cmd/compile/internal/gc/ssa.go会处理程序中的defer,该函数会根据条件不同,使用三种机制来处理该关键字

func (s *state) stmt(n *Node) {...switch n.Op {case ODEFER:if Debug_defer > 0 {var defertype stringif s.hasOpenDefers {defertype = "open-coded"} else if n.Esc == EscNever {defertype = "stack-allocated"} else {defertype = "heap-allocated"}Warnl(n.Pos, "%s defer", defertype)}if s.hasOpenDefers { // 开放编码s.openDeferRecord(n.Left) } else { // 堆分配d := callDeferif n.Esc == EscNever { // 栈分配d = callDeferStack}s.call(n.Left, d)}}}

开放编码、堆分配和栈分配是defer关键字的三种方法,而Go1.14加入的开放编码,使得关键字开销可以忽略不计。

2.2、堆分配

call方法会为所有函数和方法调用生成中间代码,工作内容:

获取需要执行的函数名、闭包指针、代码指针和函数调用的接收方获取栈地址并将函数或方法写入栈中调用newValue1A及相关函数生成函数调用的中间代码如果当前函数的调用函数是defer,那么会单独生成相关的结束代码块获取函数的返回值并结束当前调用

func (s *state) call(n *Node, k callKind) *ssa.Value {var call *ssa.Valueif k == callDeferStack { // 在栈上初始化defer结构体 ... } else { ... switch { case k == callDefer:call = s.newValue1A(ssa.OpStaticCall, types.TypeMem, deferproc, s.mem()) ... } call.AuxInt = stksize } s.vars[&memVar] = call}

defer关键字在运行时会调用deferproc,这个函数实现在src/runtime/panic.go里,接受两个参数:参数的大小和闭包所在的地址。

编译器不仅将defer关键字转成deferproc函数,还会通过以下三种方式为所有调用defer的函数末尾插入deferreturn的函数调用

1、在cmd/compile/internal/gc/walk.go的walkstmt函数中,在遇到ODEFFER节点时会执行Curfn.Func.SetHasDefer(true),设置当前函数的hasdefer属性

2、在ssa.go的buildssa会执行s.hasdefer = fn.Func.HasDefer()更新hasdefer

3、在exit中会根据hasdefer在函数返回前插入deferreturn的函数调用

func (s *state) exit() *ssa.Block {if s.hasdefer {if s.hasOpenDefers {...} else {s.rtcall(Deferreturn, true, nil)}} ...}2.2.1、创建延迟调用

runtime.deferproc为defer创建了一个runtime._defer结构体、设置它的函数指针fn、程序计数器pc和栈指针sp并将相关参数拷贝到相邻的内存空间中

func deferproc(siz int32, fn *funcval) { // arguments of fn follow fngp := getg()if gp.m.curg != gp {// go code on the system stack can't deferthrow("defer on system stack")}// 获取caller的sp寄存器和pc寄存器值sp := getcallersp()argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn)callerpc := getcallerpc()// 分配 _defer 结构d := newdefer(siz)if d._panic != nil {throw("deferproc: d.panic != nil after newdefer")}// _defer 结构初始化d.link = gp._defergp._defer = dd.fn = fnd.pc = callerpcd.sp = spswitch siz {case 0:// Do nothing.case sys.PtrSize:*(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp))default:memmove(deferArgs(d), unsafe.Pointer(argp), uintptr(siz))}return0()}

最后调用的return0是唯一一个不会触发延迟调用的函数,可以避免deferreturn的递归调用。

newdefer的分配方式是从pool缓存池中获取:

从调度器的deferpool中取出结构体并将该结构体加入到当前goroutine的缓存池中从goroutine的deferpool中取出结构体通过mallocgc从堆上创建一个新的结构体

func newdefer(siz int32) *_defer {var d *_defersc := deferclass(uintptr(siz))gp := getg() // 获取当前的goroutine// 从pool缓存池中分配,如果没有则mallocgc从堆上分配内存if sc < uintptr(len(p{}.deferpool)) {pp := gp.m.p.ptr()if len(pp.deferpool[sc]) == 0 && sched.deferpool[sc] != nil {for len(pp.deferpool[sc]) < cap(pp.deferpool[sc])/2 && sched.deferpool[sc] != nil {d := sched.deferpool[sc]sched.deferpool[sc] = d.linkd.link = nilpp.deferpool[sc] = append(pp.deferpool[sc], d)}}if n := len(pp.deferpool[sc]); n > 0 {d = pp.deferpool[sc][n-1]pp.deferpool[sc][n-1] = nilpp.deferpool[sc] = pp.deferpool[sc][:n-1]}}if d == nil {total := roundupsize(totaldefersize(uintptr(siz)))d = (*_defer)(mallocgc(total, deferType, true))}d.siz = sizd.heap = truereturn d}

这三种方式取到的结构体_defer,都会被添加到链表的队头,这也是为什么defer按照后进先出的顺序执行。

2.2.2、执行延迟调用

func deferreturn(arg0 uintptr) { // 获取当前的goroutinegp := getg() // 拷贝延迟函数到变量d上d := gp._defer // 如果延迟函数不存在则返回if d == nil {return}// 获取caller函数的sp寄存器sp := getcallersp()if d.sp != sp { // 说明这个_defer 不是在该caller函数注册的return}switch d.siz {case 0:// Do nothing.case sys.PtrSize:*(*uintptr)(unsafe.Pointer(&arg0)) = *(*uintptr)(deferArgs(d))default:memmove(unsafe.Pointer(&arg0), deferArgs(d), uintptr(d.siz))} // 获取要调用的函数fn := d.fn // 重置函数d.fn = nil // 把下一个_defer结构体依附到goroutine上gp._defer = d.link // 释放_defer(主要是堆回收,因为栈上的随函数执行完会自动回收)freedefer(d) _ = fn.fn // 调用该函数jmpdefer(fn, uintptr(unsafe.Pointer(&arg0))) }

deferreturn就是从链表的队头取出并调用jmpdefer传入需要执行的函数和参数。

该函数只有在所有延迟函数都执行后才会返回。

2.3、栈分配

如果我们能够将部分结构体分配到栈上就可以节约内存分配带来的额外开销。

在call函数中有在栈上分配

func (s *state) call(n *Node, k callKind) *ssa.Value {var call *ssa.Valueif k == callDeferStack { t := deferstruct(stksize) ... call = s.newValue1A(ssa.OpStaticCall, types.TypeMem, deferprocStack, s.mem())if stksize < int64(width="360px",height="auto" />

在运行期间deferprocStack只需要设置一些未在编译期间初始化的字段,就可以将栈上的_defer追加到函数的链表上。

func deferprocStack(d *_defer) {gp := getg()if gp.m.curg != gp {// go code on the system stack can't deferthrow("defer on system stack")}// siz 和 fn在进入到这个函数之前已经赋值d.started = false// 标明是栈的内存d.heap = falsed.openDefer = false// 获取到caller函数的sp寄存器值和pc寄存器d.sp = getcallersp()d.pc = getcallerpc()d.framepc = 0d.varp = 0*(*uintptr)(unsafe.Pointer(&d._panic)) = 0*(*uintptr)(unsafe.Pointer(&d.fd)) = 0*(*uintptr)(unsafe.Pointer(&d.link)) = uintptr(unsafe.Pointer(gp._defer))*(*uintptr)(unsafe.Pointer(&gp._defer)) = uintptr(unsafe.Pointer(d))return0()}

除了分配的位置和堆的不同,其他的大致相同。

2.4、开放编码

Go语言在1.14中通过开放编码实现defer关键字,使用代码内联优化defer关键的额外开销并引入函数数据funcdata管理panic的调用,该优化可以将 defer 的调用开销从 1.13 版本的 ~35ns 降低至 ~6ns 左右。

然而开放编码作为一种优化 defer 关键字的方法,它不是在所有的场景下都会开启的,开放编码只会在满足以下的条件时启用:

函数的 defer 数量少于或者等于 8 个;函数的 defer 关键字不能在循环中执行;函数的 return 语句与 defer 语句的乘积小于或者等于 15 个;2.4.1、启动优化

const maxOpenDefers = 8func walkstmt(n *Node) *Node { ... switch n.Op { case ODEFER:Curfn.Func.SetHasDefer(true)Curfn.Func.numDefers++if Curfn.Func.numDefers > maxOpenDefers {Curfn.Func.SetOpenCodedDeferDisallowed(true)}if n.Esc != EscNever {Curfn.Func.SetOpenCodedDeferDisallowed(true)}fallthrough }}

如果函数中defer关键字的数量多于8个或者defer处于循环中,那么就会禁用开放编码优化。

func buildssa(fn *Node, worker int) *ssa.Func { ...s.hasOpenDefers = Debug['N'] == 0 && s.hasdefer && !s.curfn.Func.OpenCodedDeferDisallowed() if s.hasOpenDefers &&s.curfn.Func.numReturns*s.curfn.Func.numDefers > 15 {s.hasOpenDefers = false} ...}

可以看到这里,判断编译参数不用-N,返回语句的数量和defer数量的乘积小于15,会启用开放编码优化。

2.4.2、延迟记录

延迟比特deferBitsTemp和延迟记录是使用开放编码实现defer的两个最重要的结构,一旦使用开放编码,buildssa会在栈上初始化大小为8个比特的deferBits

func buildssa(fn *Node, worker int) *ssa.Func {if s.hasOpenDefers {deferBitsTemp := tempAt(src.NoXPos, s.curfn, types.Types[TUINT8])s.deferBitsTemp = deferBitsTempstartDeferBits := s.entryNewValue0(ssa.OpConst8, types.Types[TUINT8])s.vars[&deferBitsVar] = startDeferBitss.deferBitsAddr = s.addr(deferBitsTemp, false)s.store(types.Types[TUINT8], s.deferBitsAddr, startDeferBits)s.vars[&memVar] = s.newValue1Apos(ssa.OpVarLive, types.TypeMem, deferBitsTemp, s.mem(), false)}}

延迟比特中的每一个比特位都表示该位对应的defer关键字是否需要被执行。延迟比特的作用就是标记哪些defer关键字在函数中被执行,这样就能在函数返回时根据对应的deferBits确定要执行的函数。

而deferBits的大小为8比特,所以该优化的条件就是defer的数量小于8.

而执行延迟调用的时候仍在deferreturn

func deferreturn(arg0 uintptr) { if d.openDefer {done := runOpenDeferFrame(gp, d)if !done {throw("unfinished open-coded defers in deferreturn")}gp._defer = d.linkfreedefer(d)return}}

这里做了特殊的优化,在runOpenDeferFrame执行开放编码延迟函数

1、从结构体_defer读取deferBits,执行函数等信息

2、在循环中依次读取执行函数的地址和参数信息,并通过deferBits判断是否要执行

3、调用reflectcallSave执行函数

func runOpenDeferFrame(gp *g, d *_defer) bool {done := truefd := d.fddeferBitsOffset, fd := readvarintUnsafe(fd)nDefers, fd := readvarintUnsafe(fd)deferBits := *(*uint8)(unsafe.Pointer(d.varp - uintptr(deferBitsOffset)))for i := int(nDefers) - 1; i >= 0; i-- {// 读取函数funcdata地址和参数信息var argwidth="360px",height="auto" />

三、总结

1、新加入的defer放入队头,执行defer时是从队头取函数调用,所以是后进先出

2、通过判断defer关键字、return数量来判断是否开启开放编码优化

3、调用deferproc函数创建新的延迟调用函数时,会立即拷贝函数的参数,函数的参数不会等到真正执行时计算

以上就是关于pos机显示调试版本,1.14版本defer性能大幅度提升的知识,后面我们会继续为大家整理关于pos机显示调试版本的知识,希望能够帮助到大家!

转发请带上网址:http://www.poszjia.com/newsone/66629.html

你可能会喜欢:

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 babsan@163.com 举报,一经查实,本站将立刻删除。