最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Go編譯原理之函數(shù)內(nèi)聯(lián)

 更新時(shí)間:2022年08月05日 12:03:21   作者:書旅  
這篇文章主要為大家介紹了Go編譯原理之函數(shù)內(nèi)聯(lián)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

在前一篇文章中分享了編譯器優(yōu)化的變量捕獲部分,本文分享編譯器優(yōu)化的另一個(gè)內(nèi)容—函數(shù)內(nèi)聯(lián)。函數(shù)內(nèi)聯(lián)是指將將較小的函數(shù)內(nèi)容,直接放入到調(diào)用者函數(shù)中,從而減少函數(shù)調(diào)用的開銷

函數(shù)內(nèi)聯(lián)概述

我們知道每一個(gè)高級(jí)編程語言的函數(shù)調(diào)用,成本都是在與需要為它分配棧內(nèi)存來存儲(chǔ)參數(shù)、返回值、局部變量等等,Go的函數(shù)調(diào)用的成本在于參數(shù)與返回值棧復(fù)制、較小的棧寄存器開銷以及函數(shù)序言部分的檢查棧擴(kuò)容(Go語言中的棧是可以動(dòng)態(tài)擴(kuò)容的,因?yàn)镚o在分配棧內(nèi)存不是逐漸增加的,而是一次性分配,這樣是為了避免訪問越界,它會(huì)一次性分配,當(dāng)檢查到分配的棧內(nèi)存不夠用時(shí),它會(huì)擴(kuò)容一個(gè)足夠大的??臻g,并將原來?xiàng)V械膬?nèi)容拷貝過來)

下邊寫一段代碼,通過Go的基準(zhǔn)測(cè)試來測(cè)一下函數(shù)內(nèi)聯(lián)帶來的效率提升

import "testing"
//go:noinline //禁用內(nèi)聯(lián)。如果要開啟內(nèi)聯(lián),將該行注釋去掉即可
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的編譯器其實(shí)會(huì)計(jì)算函數(shù)內(nèi)聯(lián)花費(fèi)的成本,所以只有簡單的函數(shù),才會(huì)觸發(fā)函數(shù)內(nèi)聯(lián)。在后邊函數(shù)內(nèi)聯(lián)的源碼實(shí)現(xiàn)中,我們可以看到下邊這些情況不會(huì)被內(nèi)聯(lián):

  • 遞歸函數(shù)
  • 函數(shù)前有如下注釋的:go:noinline、go:norace、go:nocheckptr、go:uintptrescapes
  • 沒有函數(shù)體
  • 函數(shù)聲明的抽象語法樹中節(jié)點(diǎn)數(shù)大于5000(我的Go版本是1.16.6)(也就是函數(shù)內(nèi)部語句太多的情況,也不會(huì)被內(nèi)聯(lián))
  • 函數(shù)中包含閉包(OCLOSURE)、range(ORANGE)、select(OSELECT)、go(OGO)、defer(ODEFER)、type(ODCLTYPE)、返回值是函數(shù)(ORETJMP)的,都不會(huì)內(nèi)聯(lián)

我們也可以構(gòu)建或編譯的時(shí)候,通過參數(shù)去控制它是否可以內(nèi)聯(lián)。如果希望程序中所有的函數(shù)都不執(zhí)行內(nèi)聯(lián)操作

go build -gcflags="-l" xxx.go
go tool compile -l xxx.go

同樣我們?cè)诰幾g時(shí),也可以查看哪些函數(shù)內(nèi)聯(lián)了,哪些函數(shù)沒內(nèi)聯(lián),以及原因是什么

go tool compile -m=2 xxx.go

看一個(gè)例子

package main
func test1(a, b int) int {
	return a+b
}
func step(n int) int {
	if n &lt; 2 {
		return n
	}
	return step(n-1) + step(n-2)
}
func main()  {
	test1(1, 2)
	step(5)
}

可以看到test1這個(gè)函數(shù)是可以內(nèi)聯(lián)的,因?yàn)樗暮瘮?shù)體很簡單。step這個(gè)函數(shù)因?yàn)槭沁f歸函數(shù),所以它不會(huì)進(jìn)行內(nèi)聯(lián)

函數(shù)內(nèi)聯(lián)底層實(shí)現(xiàn)

這里邊其實(shí)每一個(gè)函數(shù)調(diào)用鏈都很深,我這里不會(huì)一行一行的解釋代碼的含義,僅僅會(huì)將一些核心的方法拿出來介紹一下,感興趣的小伙伴可以自己去調(diào)試一下(前邊有發(fā)相關(guān)文章)(Go源碼調(diào)試方法

還是前邊提到多次的Go編譯入口文件,你可以在入口文件中找到這段代碼

Go編譯入口文件:src/cmd/compile/main.go -> gc.Main(archInit)
// Phase 5: Inlining
if Debug.l != 0 {
		// 查找可以內(nèi)聯(lián)的函數(shù)
		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)
		}
	}

下邊就看一下每個(gè)方法都在做哪些事情

visitBottomUp

該方法有兩個(gè)參數(shù):

  • xtop:前邊已經(jīng)見過它了,它存放的是每個(gè)聲明語句的抽象語法樹的根節(jié)點(diǎn)數(shù)組
  • 第二個(gè)參數(shù)是一個(gè)函數(shù)(該函數(shù)也有兩個(gè)參數(shù),一個(gè)是滿足是函數(shù)類型聲明的抽象語法樹根節(jié)點(diǎn)數(shù)組,一個(gè)是bool值,true表示是遞歸函數(shù),false表示不是遞歸函數(shù))

進(jìn)入到visitBottomUp方法中,你會(huì)發(fā)現(xiàn)它主要是遍歷xtop,并對(duì)每個(gè)抽象語法樹的根節(jié)點(diǎn)調(diào)用了visit這個(gè)方法(僅針對(duì)是函數(shù)類型聲明的抽象語法樹)

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() { //是函數(shù),并且不是閉包函數(shù)
			v.visit(n)
		}
	}
}

visit方法的核心是調(diào)用了inspectList方法,通過inspectList對(duì)抽象語法樹按照深度優(yōu)先搜索進(jìn)行遍歷,并將每一個(gè)節(jié)點(diǎn)作為inspectList方法的第二個(gè)參數(shù)(是一個(gè)函數(shù))的參數(shù),比如驗(yàn)證這個(gè)函數(shù)里邊是否有遞歸調(diào)用等(具體就是下邊的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
}

后邊通過調(diào)用visitBottomUp的第二個(gè)參數(shù)傳遞的方法,對(duì)抽象語法樹進(jìn)行內(nèi)聯(lián)的判斷及內(nèi)聯(lián)操作,具體就是caninlinlcalls這兩個(gè)方法

caninl

該方法的作用就是驗(yàn)證是函數(shù)類型聲明的抽象語法樹是否可以內(nèi)聯(lián)

這個(gè)方法的實(shí)現(xiàn)很簡單,首先是通過很多的if語句驗(yàn)證函數(shù)前邊是否有像go:noinline等這種標(biāo)記

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()),
	}
	......
}

這里邊還有一個(gè)主要的方法就是visitList,它是用來驗(yàn)證函數(shù)里邊是否有我們上邊提到的go、select、range等等這些語句。對(duì)于滿足內(nèi)聯(lián)條件的,它會(huì)將改寫該函數(shù)聲明抽閑語法樹的內(nèi)聯(lián)字段(Inl)

inlcalls

該方法中就是具體的內(nèi)聯(lián)操作,比如將函數(shù)的參數(shù)和返回值轉(zhuǎn)換為調(diào)用者中的聲明語句等。里邊的調(diào)用和實(shí)現(xiàn)都比較復(fù)雜,這里不粘代碼了,大家可自行去看。函數(shù)內(nèi)聯(lián)的核心方法都在如下文件中

src/cmd/compile/internal/gc/inl.go

以上就是Go編譯原理之函數(shù)內(nèi)聯(lián)的詳細(xì)內(nèi)容,更多關(guān)于Go編譯原理函數(shù)內(nèi)聯(lián)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • golang websocket 服務(wù)端的實(shí)現(xiàn)

    golang websocket 服務(wù)端的實(shí)現(xiàn)

    這篇文章主要介紹了golang websocket 服務(wù)端的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • 一文詳解Golang中的基礎(chǔ)語法

    一文詳解Golang中的基礎(chǔ)語法

    這篇文章主要為大家詳細(xì)介紹了Golang中基礎(chǔ)語法的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Golang有一定的幫助,感興趣的可以了解一下
    2023-03-03
  • golang字符串切片去重的幾種算法

    golang字符串切片去重的幾種算法

    這篇文章主要介紹了golang字符串切片去重的幾種算法的相關(guān)資料,需要的朋友可以參考下
    2023-09-09
  • pytorch中的transforms.ToTensor和transforms.Normalize的實(shí)現(xiàn)

    pytorch中的transforms.ToTensor和transforms.Normalize的實(shí)現(xiàn)

    本文主要介紹了pytorch中的transforms.ToTensor和transforms.Normalize的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-04-04
  • golang(gin)的全局統(tǒng)一異常處理方式,并統(tǒng)一返回json

    golang(gin)的全局統(tǒng)一異常處理方式,并統(tǒng)一返回json

    這篇文章主要介紹了golang(gin)的全局統(tǒng)一異常處理方式,并統(tǒng)一返回json,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • 基于Golang編寫一個(gè)聊天工具

    基于Golang編寫一個(gè)聊天工具

    這篇文章主要為大家詳細(xì)介紹了如何使用?Golang?構(gòu)建一個(gè)簡單但功能完善的聊天工具,利用?WebSocket?技術(shù)實(shí)現(xiàn)即時(shí)通訊的功能,需要的小伙伴可以參考下
    2023-11-11
  • gtoken替換jwt實(shí)現(xiàn)sso登錄的排雷避坑

    gtoken替換jwt實(shí)現(xiàn)sso登錄的排雷避坑

    這篇文章主要為大家介紹了gtoken替換jwt實(shí)現(xiàn)sso登錄的排雷避坑,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • go語言讀取csv文件并輸出的方法

    go語言讀取csv文件并輸出的方法

    這篇文章主要介紹了go語言讀取csv文件并輸出的方法,實(shí)例分析了go語言操作csv文件的技巧,需要的朋友可以參考下
    2015-03-03
  • 教你一招完美解決vscode安裝go插件失敗問題

    教你一招完美解決vscode安裝go插件失敗問題

    VSCode是我們開發(fā)go程序的常用工具,但是安裝VSCode成功后,創(chuàng)建一個(gè).go文件居然提示錯(cuò)誤了,所以下面下面這篇文章主要給大家介紹了如何通過一招完美解決vscode安裝go插件失敗問題的相關(guān)資料,需要的朋友可以參考下
    2022-07-07
  • Golang中的深拷貝與淺拷貝使用

    Golang中的深拷貝與淺拷貝使用

    本文主要介紹了Golang中的深拷貝與淺拷貝使用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04

最新評(píng)論

台东市| 赣州市| 德格县| 海伦市| 满洲里市| 会同县| 临武县| 新邵县| 泰和县| 博客| 炉霍县| 睢宁县| 辽阳市| 沈丘县| 塔城市| 平潭县| 台中市| 崇义县| 当阳市| 潼关县| 抚顺县| 张家口市| 乌什县| 铁岭县| 黔西| 福泉市| 岢岚县| 卓资县| 平罗县| 色达县| 乐清市| 江源县| 华阴市| 西华县| 张掖市| 泸溪县| 兖州市| 新乐市| 西吉县| 宣城市| 高青县|