<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在前一篇文章中分享了編譯器優化的變數捕獲部分,本文分享編譯器優化的另一個內容—函數內聯。函數內聯是指將將較小的函數內容,直接放入到呼叫者函數中,從而減少函數呼叫的開銷
我們知道每一個高階程式語言的函數呼叫,成本都是在與需要為它分配棧記憶體來儲存引數、返回值、區域性變數等等,Go的函數呼叫的成本在於引數與返回值棧複製、較小的棧暫存器開銷以及函數序言部分的檢查棧擴容(Go語言中的棧是可以動態擴容的,因為Go在分配棧記憶體不是逐漸增加的,而是一次性分配,這樣是為了避免存取越界,它會一次性分配,當檢查到分配的棧記憶體不夠用時,它會擴容一個足夠大的棧空間,並將原來棧中的內容拷貝過來)
下邊寫一段程式碼,通過Go的基準測試來測一下函數內聯帶來的效率提升
import "testing" //go:noinline //禁用內聯。如果要開啟內聯,將該行註釋去掉即可 func max(a, b int) int { if a > b { return a } return b } var Result int func BenchmarkMax(b *testing.B) { var r int for i:=0; i< b.N; i++ { r = max(-1, i) } Result = r }
在編譯的過程中,Go的編譯器其實會計算函數內聯花費的成本,所以只有簡單的函數,才會觸發函數內聯。在後邊函數內聯的原始碼實現中,我們可以看到下邊這些情況不會被內聯:
go:noinline
、go:norace
、go:nocheckptr
、go:uintptrescapes
等OCLOSURE
)、range(ORANGE
)、select(OSELECT
)、go(OGO
)、defer(ODEFER
)、type(ODCLTYPE
)、返回值是函數(ORETJMP
)的,都不會內聯我們也可以構建或編譯的時候,通過引數去控制它是否可以內聯。如果希望程式中所有的函數都不執行內聯操作
go build -gcflags="-l" xxx.go go tool compile -l xxx.go
同樣我們在編譯時,也可以檢視哪些函數內聯了,哪些函數沒內聯,以及原因是什麼
go tool compile -m=2 xxx.go
看一個例子
package main func test1(a, b int) int { return a+b } func step(n int) int { if n < 2 { return n } return step(n-1) + step(n-2) } func main() { test1(1, 2) step(5) }
可以看到test1這個函數是可以內聯的,因為它的函數體很簡單。step這個函數因為是遞迴函數,所以它不會進行內聯
這裡邊其實每一個函數呼叫鏈都很深,我這裡不會一行一行的解釋程式碼的含義,僅僅會將一些核心的方法拿出來介紹一下,感興趣的小夥伴可以自己去偵錯一下(前邊有發相關文章)(Go原始碼偵錯方法)
還是前邊提到多次的Go編譯入口檔案,你可以在入口檔案中找到這段程式碼
Go編譯入口檔案:src/cmd/compile/main.go -> gc.Main(archInit) // Phase 5: Inlining if Debug.l != 0 { // 查詢可以內聯的函數 visitBottomUp(xtop, func(list []*Node, recursive bool) { numfns := numNonClosures(list) for _, n := range list { if !recursive || numfns > 1 { caninl(n) } else { ...... } inlcalls(n) } }) } for _, n := range xtop { if n.Op == ODCLFUNC { devirtualize(n) } }
下邊就看一下每個方法都在做哪些事情
該方法有兩個引數:
xtop
:前邊已經見過它了,它存放的是每個宣告語句的抽象語法樹的根節點陣列進入到visitBottomUp方法中,你會發現它主要是遍歷xtop,並對每個抽象語法樹的根節點呼叫了visit
這個方法(僅針對是函數型別宣告的抽象語法樹)
func visitBottomUp(list []*Node, analyze func(list []*Node, recursive bool)) { var v bottomUpVisitor v.analyze = analyze v.nodeID = make(map[*Node]uint32) for _, n := range list { if n.Op == ODCLFUNC && !n.Func.IsHiddenClosure() { //是函數,並且不是閉包函數 v.visit(n) } } }
而visit
方法的核心是呼叫了inspectList
方法,通過inspectList
對抽象語法樹按照深度優先搜尋進行遍歷,並將每一個節點作為inspectList
方法的第二個引數(是一個函數)的引數,比如驗證這個函數裡邊是否有遞迴呼叫等(具體就是下邊的switch case)
func (v *bottomUpVisitor) visit(n *Node) uint32 { if id := v.nodeID[n]; id > 0 { // already visited return id } ...... v.stack = append(v.stack, n) inspectList(n.Nbody, func(n *Node) bool { switch n.Op { case ONAME: if n.Class() == PFUNC { ...... } case ODOTMETH: fn := asNode(n.Type.Nname()) ...... } case OCALLPART: fn := asNode(callpartMethod(n).Type.Nname()) ...... case OCLOSURE: if m := v.visit(n.Func.Closure); m < min { min = m } } return true }) v.analyze(block, recursive) } return min }
後邊通過呼叫visitBottomUp
的第二個引數傳遞的方法,對抽象語法樹進行內聯的判斷及內聯操作,具體就是caninl
和inlcalls
這兩個方法
該方法的作用就是驗證是函數型別宣告的抽象語法樹是否可以內聯
這個方法的實現很簡單,首先是通過很多的if語句驗證函數前邊是否有像go:noinline
等這種標記
func caninl(fn *Node) { if fn.Op != ODCLFUNC { Fatalf("caninl %v", fn) } if fn.Func.Nname == nil { Fatalf("caninl no nname %+v", fn) } var reason string // reason, if any, that the function was not inlined ...... // If marked "go:noinline", don't inline if fn.Func.Pragma&Noinline != 0 { reason = "marked go:noinline" return } // If marked "go:norace" and -race compilation, don't inline. if flag_race && fn.Func.Pragma&Norace != 0 { reason = "marked go:norace with -race compilation" return } ...... // If fn has no body (is defined outside of Go), cannot inline it. if fn.Nbody.Len() == 0 { reason = "no function body" return } visitor := hairyVisitor{ budget: inlineMaxBudget, extraCallCost: cc, usedLocals: make(map[*Node]bool), } if visitor.visitList(fn.Nbody) { reason = visitor.reason return } if visitor.budget < 0 { reason = fmt.Sprintf("function too complex: cost %d exceeds budget %d", inlineMaxBudget-visitor.budget, inlineMaxBudget) return } n.Func.Inl = &Inline{ Cost: inlineMaxBudget - visitor.budget, Dcl: inlcopylist(pruneUnusedAutos(n.Name.Defn.Func.Dcl, &visitor)), Body: inlcopylist(fn.Nbody.Slice()), } ...... }
這裡邊還有一個主要的方法就是visitList
,它是用來驗證函數裡邊是否有我們上邊提到的go、select、range等等這些語句。對於滿足內聯條件的,它會將改寫該函數宣告抽閒語法樹的內聯欄位(Inl
)
該方法中就是具體的內聯操作,比如將函數的引數和返回值轉換為呼叫者中的宣告語句等。裡邊的呼叫和實現都比較複雜,這裡不粘程式碼了,大家可自行去看。函數內聯的核心方法都在如下檔案中
src/cmd/compile/internal/gc/inl.go
以上就是Go編譯原理之函數內聯的詳細內容,更多關於Go編譯原理函數內聯的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45