pos機(jī)顯示調(diào)試版本,1.14版本defer性能大幅度提升

 新聞資訊2  |   2023-06-12 09:08  |  投稿人:pos機(jī)之家

網(wǎng)上有很多關(guān)于pos機(jī)顯示調(diào)試版本,1.14版本defer性能大幅度提升的知識,也有很多人為大家解答關(guān)于pos機(jī)顯示調(diào)試版本的問題,今天pos機(jī)之家(m.afbey.com)為大家整理了關(guān)于這方面的知識,讓我們一起來看下吧!

本文目錄一覽:

1、pos機(jī)顯示調(diào)試版本

pos機(jī)顯示調(diào)試版本

你的留言,是我堅持分享的動力。如果你有任何疑問,可以點擊關(guān)注,點贊、留言提問。

GO中的defer會在當(dāng)前函數(shù)返回前執(zhí)行傳入的函數(shù),常用于關(guān)閉文件描述符,關(guān)閉鏈接及解鎖等操作。

Go語言中使用defer時會遇到兩個常見問題:

defer的調(diào)用時機(jī)和執(zhí)行順序defer調(diào)用函數(shù)使用傳值的方法會執(zhí)行函數(shù),導(dǎo)致不符預(yù)期的結(jié)果

接下來我們來詳細(xì)處理這兩個問題。

一、defer

官方有段對defer的解釋:

每次defer語句執(zhí)行時,會把函數(shù)“壓?!?,函數(shù)的參數(shù)會被拷貝下來,當(dāng)外層函數(shù)退出時,defer函數(shù)按照定義的逆序執(zhí)行,如果defer執(zhí)行的函數(shù)為nil,那么會在最終調(diào)用函數(shù)產(chǎn)生panic。

這里我們先來一道經(jīng)典的面試題

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}

你覺得這個會打印什么?

輸出結(jié)果:

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

這里是遵循先入后出的原則,同時保留當(dāng)前變量的值。

把這道題簡化一下:

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

輸出結(jié)果

1

上述代碼輸出似乎不符合預(yù)期,這個現(xiàn)象出現(xiàn)的原因是什么呢?經(jīng)過分析,我們發(fā)現(xiàn)調(diào)用defer關(guān)鍵字會立即拷貝函數(shù)中引用的外部參數(shù),所以fmt.Println(i)的這個i是在調(diào)用defer的時候就已經(jīng)賦值了,所以會直接打印1。

想要解決這個問題也很簡單,只需要向defer關(guān)鍵字傳入匿名函數(shù)

func main() {var i = 1defer func() {fmt.Println(i)}()i = 4}二、數(shù)據(jù)結(jié)構(gòu)

// 占用48字節(jié)的內(nèi)存,link把所有的_defer串成一個鏈表。// _defer 只是一個header,結(jié)構(gòu)緊跟的是延遲函數(shù)的參數(shù)和返回值的空間,大小由 siz 決定type _defer struct {// 參數(shù)和返回值的內(nèi)存大小siz int32 started bool// 區(qū)分該結(jié)構(gòu)是在棧上分配還是在堆上分配heap boolopenDefer bool// sp 計數(shù)器值,棧指針sp uintptr // sp at time of defer// pc 計數(shù)器值,程序計數(shù)器pc uintptr // pc at time of defer// defer 傳入的函數(shù)地址,也就是延后執(zhí)行的函數(shù)fn *funcval // can be nil for open-coded defers // 觸發(fā)延遲調(diào)用的結(jié)構(gòu)體_panic *_panic // 下一個要被執(zhí)行的延遲函數(shù)link *_defer }

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

2.1、執(zhí)行機(jī)制

中間代碼生成階段cmd/compile/internal/gc/ssa.go會處理程序中的defer,該函數(shù)會根據(jù)條件不同,使用三種機(jī)制來處理該關(guān)鍵字

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關(guān)鍵字的三種方法,而Go1.14加入的開放編碼,使得關(guān)鍵字開銷可以忽略不計。

2.2、堆分配

call方法會為所有函數(shù)和方法調(diào)用生成中間代碼,工作內(nèi)容:

獲取需要執(zhí)行的函數(shù)名、閉包指針、代碼指針和函數(shù)調(diào)用的接收方獲取棧地址并將函數(shù)或方法寫入棧中調(diào)用newValue1A及相關(guān)函數(shù)生成函數(shù)調(diào)用的中間代碼如果當(dāng)前函數(shù)的調(diào)用函數(shù)是defer,那么會單獨生成相關(guān)的結(jié)束代碼塊獲取函數(shù)的返回值并結(jié)束當(dāng)前調(diào)用

func (s *state) call(n *Node, k callKind) *ssa.Value {var call *ssa.Valueif k == callDeferStack { // 在棧上初始化defer結(jié)構(gòu)體 ... } else { ... switch { case k == callDefer:call = s.newValue1A(ssa.OpStaticCall, types.TypeMem, deferproc, s.mem()) ... } call.AuxInt = stksize } s.vars[&memVar] = call}

defer關(guān)鍵字在運(yùn)行時會調(diào)用deferproc,這個函數(shù)實現(xiàn)在src/runtime/panic.go里,接受兩個參數(shù):參數(shù)的大小和閉包所在的地址。

編譯器不僅將defer關(guān)鍵字轉(zhuǎn)成deferproc函數(shù),還會通過以下三種方式為所有調(diào)用defer的函數(shù)末尾插入deferreturn的函數(shù)調(diào)用

1、在cmd/compile/internal/gc/walk.go的walkstmt函數(shù)中,在遇到ODEFFER節(jié)點時會執(zhí)行Curfn.Func.SetHasDefer(true),設(shè)置當(dāng)前函數(shù)的hasdefer屬性

2、在ssa.go的buildssa會執(zhí)行s.hasdefer = fn.Func.HasDefer()更新hasdefer

3、在exit中會根據(jù)hasdefer在函數(shù)返回前插入deferreturn的函數(shù)調(diào)用

func (s *state) exit() *ssa.Block {if s.hasdefer {if s.hasOpenDefers {...} else {s.rtcall(Deferreturn, true, nil)}} ...}2.2.1、創(chuàng)建延遲調(diào)用

runtime.deferproc為defer創(chuàng)建了一個runtime._defer結(jié)構(gòu)體、設(shè)置它的函數(shù)指針fn、程序計數(shù)器pc和棧指針sp并將相關(guān)參數(shù)拷貝到相鄰的內(nèi)存空間中

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 結(jié)構(gòu)d := newdefer(siz)if d._panic != nil {throw("deferproc: d.panic != nil after newdefer")}// _defer 結(jié)構(gòu)初始化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()}

最后調(diào)用的return0是唯一一個不會觸發(fā)延遲調(diào)用的函數(shù),可以避免deferreturn的遞歸調(diào)用。

newdefer的分配方式是從pool緩存池中獲?。?/p>從調(diào)度器的deferpool中取出結(jié)構(gòu)體并將該結(jié)構(gòu)體加入到當(dāng)前goroutine的緩存池中從goroutine的deferpool中取出結(jié)構(gòu)體通過mallocgc從堆上創(chuàng)建一個新的結(jié)構(gòu)體

func newdefer(siz int32) *_defer {var d *_defersc := deferclass(uintptr(siz))gp := getg() // 獲取當(dāng)前的goroutine// 從pool緩存池中分配,如果沒有則mallocgc從堆上分配內(nèi)存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}

這三種方式取到的結(jié)構(gòu)體_defer,都會被添加到鏈表的隊頭,這也是為什么defer按照后進(jìn)先出的順序執(zhí)行。

2.2.2、執(zhí)行延遲調(diào)用

func deferreturn(arg0 uintptr) { // 獲取當(dāng)前的goroutinegp := getg() // 拷貝延遲函數(shù)到變量d上d := gp._defer // 如果延遲函數(shù)不存在則返回if d == nil {return}// 獲取caller函數(shù)的sp寄存器sp := getcallersp()if d.sp != sp { // 說明這個_defer 不是在該caller函數(shù)注冊的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))} // 獲取要調(diào)用的函數(shù)fn := d.fn // 重置函數(shù)d.fn = nil // 把下一個_defer結(jié)構(gòu)體依附到goroutine上gp._defer = d.link // 釋放_defer(主要是堆回收,因為棧上的隨函數(shù)執(zhí)行完會自動回收)freedefer(d) _ = fn.fn // 調(diào)用該函數(shù)jmpdefer(fn, uintptr(unsafe.Pointer(&arg0))) }

deferreturn就是從鏈表的隊頭取出并調(diào)用jmpdefer傳入需要執(zhí)行的函數(shù)和參數(shù)。

該函數(shù)只有在所有延遲函數(shù)都執(zhí)行后才會返回。

2.3、棧分配

如果我們能夠?qū)⒉糠纸Y(jié)構(gòu)體分配到棧上就可以節(jié)約內(nèi)存分配帶來的額外開銷。

在call函數(shù)中有在棧上分配

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

在運(yùn)行期間deferprocStack只需要設(shè)置一些未在編譯期間初始化的字段,就可以將棧上的_defer追加到函數(shù)的鏈表上。

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在進(jìn)入到這個函數(shù)之前已經(jīng)賦值d.started = false// 標(biāo)明是棧的內(nèi)存d.heap = falsed.openDefer = false// 獲取到caller函數(shù)的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中通過開放編碼實現(xiàn)defer關(guān)鍵字,使用代碼內(nèi)聯(lián)優(yōu)化defer關(guān)鍵的額外開銷并引入函數(shù)數(shù)據(jù)funcdata管理panic的調(diào)用,該優(yōu)化可以將 defer 的調(diào)用開銷從 1.13 版本的 ~35ns 降低至 ~6ns 左右。

然而開放編碼作為一種優(yōu)化 defer 關(guān)鍵字的方法,它不是在所有的場景下都會開啟的,開放編碼只會在滿足以下的條件時啟用:

函數(shù)的 defer 數(shù)量少于或者等于 8 個;函數(shù)的 defer 關(guān)鍵字不能在循環(huán)中執(zhí)行;函數(shù)的 return 語句與 defer 語句的乘積小于或者等于 15 個;2.4.1、啟動優(yōu)化

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

如果函數(shù)中defer關(guān)鍵字的數(shù)量多于8個或者defer處于循環(huán)中,那么就會禁用開放編碼優(yōu)化。

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

可以看到這里,判斷編譯參數(shù)不用-N,返回語句的數(shù)量和defer數(shù)量的乘積小于15,會啟用開放編碼優(yōu)化。

2.4.2、延遲記錄

延遲比特deferBitsTemp和延遲記錄是使用開放編碼實現(xiàn)defer的兩個最重要的結(jié)構(gòu),一旦使用開放編碼,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)}}

延遲比特中的每一個比特位都表示該位對應(yīng)的defer關(guān)鍵字是否需要被執(zhí)行。延遲比特的作用就是標(biāo)記哪些defer關(guān)鍵字在函數(shù)中被執(zhí)行,這樣就能在函數(shù)返回時根據(jù)對應(yīng)的deferBits確定要執(zhí)行的函數(shù)。

而deferBits的大小為8比特,所以該優(yōu)化的條件就是defer的數(shù)量小于8.

而執(zhí)行延遲調(diào)用的時候仍在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}}

這里做了特殊的優(yōu)化,在runOpenDeferFrame執(zhí)行開放編碼延遲函數(shù)

1、從結(jié)構(gòu)體_defer讀取deferBits,執(zhí)行函數(shù)等信息

2、在循環(huán)中依次讀取執(zhí)行函數(shù)的地址和參數(shù)信息,并通過deferBits判斷是否要執(zhí)行

3、調(diào)用reflectcallSave執(zhí)行函數(shù)

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-- {// 讀取函數(shù)funcdata地址和參數(shù)信息var argwidth="360px",height="auto" />

三、總結(jié)

1、新加入的defer放入隊頭,執(zhí)行defer時是從隊頭取函數(shù)調(diào)用,所以是后進(jìn)先出

2、通過判斷defer關(guān)鍵字、return數(shù)量來判斷是否開啟開放編碼優(yōu)化

3、調(diào)用deferproc函數(shù)創(chuàng)建新的延遲調(diào)用函數(shù)時,會立即拷貝函數(shù)的參數(shù),函數(shù)的參數(shù)不會等到真正執(zhí)行時計算

以上就是關(guān)于pos機(jī)顯示調(diào)試版本,1.14版本defer性能大幅度提升的知識,后面我們會繼續(xù)為大家整理關(guān)于pos機(jī)顯示調(diào)試版本的知識,希望能夠幫助到大家!

轉(zhuǎn)發(fā)請帶上網(wǎng)址:http://m.afbey.com/newsone/66629.html

你可能會喜歡:

版權(quán)聲明:本文內(nèi)容由互聯(lián)網(wǎng)用戶自發(fā)貢獻(xiàn),該文觀點僅代表作者本人。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如發(fā)現(xiàn)本站有涉嫌抄襲侵權(quán)/違法違規(guī)的內(nèi)容, 請發(fā)送郵件至 babsan@163.com 舉報,一經(jīng)查實,本站將立刻刪除。