真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

go語言內(nèi)聯(lián) go語言nil

為什么我不會舍棄Python投奔Go語言

在Go語言中,規(guī)定的方式是,函數(shù)返回錯誤信息。這沒什么。如果一個文件并不存在,op.Open函數(shù)會返回一個錯誤信息。這沒什么。如果你向你一個中斷了的網(wǎng)絡(luò)連接里寫數(shù)據(jù),net.Conn里的Write方法會返回一個錯誤。這沒什么。這種狀況在這種程序中是可以預(yù)料到的。這種操作就是容易失敗,你知道程序會如何運(yùn)行,因?yàn)锳PI的設(shè)計(jì)者通過內(nèi)置了一種錯誤情況的結(jié)果而讓這一切顯得很清楚。

網(wǎng)站建設(shè)哪家好,找成都創(chuàng)新互聯(lián)!專注于網(wǎng)頁設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、小程序定制開發(fā)、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項(xiàng)目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了雙江免費(fèi)建站歡迎大家使用!

從另一方面講,有些操作基本上不會出錯,所處的環(huán)境根本不可能給你提示錯誤信息,不可能控制錯誤。這才是讓人痛苦的地方。典型的例子;一個程序執(zhí)行

x[j],j值超出數(shù)組邊界,這才痛苦。像這樣預(yù)料之外的麻煩在程序中是一個嚴(yán)重的bug,一般會弄死程序的運(yùn)行。不幸的是,由于這種情況的存在,我們很難寫出健壯的,具有自我防御的服務(wù)器——例如,可以應(yīng)付偶然出現(xiàn)的有bug的HTTP請求處理器時(shí),不影響其他服務(wù)的啟動和運(yùn)行。為解決這個問題,我們引入了恢復(fù)機(jī)制,它能讓一個go例程從錯誤中恢復(fù),服務(wù)余下設(shè)定的調(diào)用。然而,代價(jià)是,至少會丟失一個調(diào)用。這是特意而為之的。引用郵件中的原話:“這種設(shè)計(jì)不同于常見的異??刂平Y(jié)構(gòu),這是一個認(rèn)真思考后的決定。我們不希望像java語言里那樣把錯誤和異常混為一談?!?/p>

我剛開始提到的那篇文章里問“為什么數(shù)組越界造成的麻煩會比錯誤的網(wǎng)址或斷掉的網(wǎng)絡(luò)引出的問題要大?”答案是,我們沒有一種內(nèi)聯(lián)并行的方法來報(bào)告在執(zhí)行x[j]期間產(chǎn)生的錯誤,但我們有內(nèi)聯(lián)并行的方法報(bào)告由錯誤網(wǎng)址或網(wǎng)絡(luò)問題造成的錯誤。

使用Go語言中的錯誤返回模式的規(guī)則很簡單:如果你的函數(shù)在某種情況下很容易出錯,那它就應(yīng)該返回錯誤。當(dāng)我調(diào)用其它的程序庫時(shí),如果它是這樣寫的,那我不必?fù)?dān)心那些錯誤的產(chǎn)生,除非有真正異常的狀況,我根本沒有想到需要處理它們。

有一個你需要記在心里的事情是,Go語言是為大型軟件設(shè)計(jì)的。我們都喜歡程序簡潔清晰,但對于一個由很多程序員一起開發(fā)的大型軟件,維護(hù)成本的增加很難讓程序簡潔。異常捕捉模式的錯誤處理方式的一個很有吸引力的特點(diǎn)是,它非常適合小程序。但對于大型程序庫,如果對于一些普通操作,你都需要考慮每行代碼是否會拋出異常、是否有必要捕捉處理,這對于開發(fā)效率和程序員的時(shí)間來說都是非常嚴(yán)重的拖累。我自己做開發(fā)大型Python軟件時(shí)感受到了這個問題。

Go語言的返回錯誤方式,不可否認(rèn),對于調(diào)用者不是很方便,但這樣做會讓程序中可能會出錯的地方顯的很明顯。對于小程序來說,你可能只想打印出錯誤,退出程序。對于一些很精密的程序,根據(jù)異常的不同,來源的不同,程序會做出不同的反應(yīng),這很常見,這種情況中,try

+

catch的方式相對于錯誤返回模式顯得冗長。當(dāng)然,Python里的一個10行的代碼放到Go語言里很可能會更冗長。畢竟,Go語言主要不是針對10行規(guī)模的程序的。

就是要說明這一點(diǎn):Go語言程序員認(rèn)為,把error作為一種內(nèi)置的類型是非常重要的。

1.14版本defer性能大幅度提升,內(nèi)部實(shí)現(xiàn)了開放編碼優(yōu)化

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

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

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

官方有段對defer的解釋:

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

你覺得這個會打印什么?

輸出結(jié)果:

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

把這道題簡化一下:

輸出結(jié)果

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

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

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

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

開放編碼、堆分配和棧分配是defer關(guān)鍵字的三種方法,而Go1.14加入的開放編碼,使得關(guān)鍵字開銷可以忽略不計(jì)。

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

defer關(guān)鍵字在運(yùn)行時(shí)會調(diào)用deferproc,這個函數(shù)實(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é)點(diǎn)時(shí)會執(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)用

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

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

newdefer的分配方式是從pool緩存池中獲?。?/p>

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

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

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

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

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

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

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

Go語言在1.14中通過開放編碼實(shí)現(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í)啟用:

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

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

延遲比特deferBitsTemp和延遲記錄是使用開放編碼實(shí)現(xiàn)defer的兩個最重要的結(jié)構(gòu),一旦使用開放編碼,buildssa會在棧上初始化大小為8個比特的deferBits

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

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

而執(zhí)行延遲調(diào)用的時(shí)候仍在deferreturn

這里做了特殊的優(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ù)

1、新加入的defer放入隊(duì)頭,執(zhí)行defer時(shí)是從隊(duì)頭取函數(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ù)的參數(shù)不會等到真正執(zhí)行時(shí)計(jì)算

bpftrace動態(tài)追蹤golang應(yīng)用-函數(shù)內(nèi)聯(lián)問題

在上一篇文章的golang代碼中,函數(shù)add的上一行,增加了一條注釋語句: //go:noinline 。在bpftrace追蹤時(shí),是否可以去掉?有什么作用?

為了說明該問題,設(shè)計(jì)一個例子。

golang代碼中,有兩個求和函數(shù)。其中,add1加上 //go:noinline ,另一個add2不加。代碼如下:

bpftrace程序分別對函數(shù)add1和add2的輸入?yún)?shù)、返回值進(jìn)行追蹤,代碼如下:

執(zhí)行程序后,可以看到bpftrace程序能夠正常追蹤到函數(shù)add1,但是無法追蹤到函數(shù)add2。

通過上文中的示例代碼,可以看到,沒有加 //go:noinline 的函數(shù)無法被bpftrace程序追蹤到。通過查閱golang相關(guān)文檔,可以知道, //go:noinline 表示該函數(shù)在編譯時(shí),不會被內(nèi)聯(lián)。

使用 objump -S 生成golang程序的匯編代碼如下:

通過匯編代碼,我們可以看到,主函數(shù)中,地址 0x498e52 處 callq 498e00 調(diào)用了add1函數(shù),地址 0x498ebb 處 movq $0x4,(%rsp) 直接計(jì)算求值。

因此,golang編譯器在編譯代碼時(shí),會對代碼進(jìn)行分析,并按照內(nèi)聯(lián)規(guī)則,將某些函數(shù)生成內(nèi)聯(lián)代碼。一旦函數(shù)被內(nèi)聯(lián),bpftrace將無法追蹤到對應(yīng)函數(shù)。也就是,上文中函數(shù) add2 無法被追蹤到。

針對golang程序中編譯器內(nèi)聯(lián)的問題,可以通過禁止內(nèi)聯(lián)的方式來解決。禁止內(nèi)聯(lián)的方式有:

在實(shí)踐中,可以通過 go build -gcflags="-m -m" 來查看,哪些函數(shù)會在編譯時(shí)執(zhí)行內(nèi)聯(lián),如:

從輸出中,可以看到:

關(guān)于golang編譯器進(jìn)行內(nèi)聯(lián)的場景,可以參考golang源碼:。

由于golang編譯器內(nèi)聯(lián)優(yōu)化,bpftrace可能無法正常追蹤golang程序。在編寫bpftrace腳本時(shí),可以先使用 nm 命令查看一下可執(zhí)行程序,是否存在需要追蹤的函數(shù)的符號信息。如果沒有則bpftrace將不能對其進(jìn)行追蹤。

前面的示例中,都是對 int 類型的參數(shù)進(jìn)行追蹤,那對于 string 類型的參數(shù),是否也可以用同樣的方式進(jìn)行追蹤?將在下一篇中進(jìn)行討論。


網(wǎng)站標(biāo)題:go語言內(nèi)聯(lián) go語言nil
文章地址:http://weahome.cn/article/doeddjj.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部