Go?內(nèi)聯(lián)優(yōu)化讓程序員愛(ài)不釋手
前言:
這是一篇介紹 Go 編譯器如何實(shí)現(xiàn)內(nèi)聯(lián)的文章,以及這種優(yōu)化將如何影響你的 Go 代碼。
什么是內(nèi)聯(lián)?
內(nèi)聯(lián)是將較小的函數(shù)合并到它們各自的調(diào)用者中的行為。其在不同的計(jì)算歷史時(shí)期的做法不一樣,如下:
- 早期:這種優(yōu)化通常是由手工完成的。
- 現(xiàn)在:內(nèi)聯(lián)是在編譯過(guò)程中自動(dòng)進(jìn)行的一類(lèi)基本優(yōu)化之一。
為什么內(nèi)聯(lián)很重要?
內(nèi)聯(lián)是很重要的,每一門(mén)語(yǔ)言都必然會(huì)有。
具體的原因如下:
- 它消除了函數(shù)調(diào)用本身的開(kāi)銷(xiāo)。
- 它允許編譯器更有效地應(yīng)用其他優(yōu)化策略。
核心來(lái)講,就是性能更好了。
函數(shù)調(diào)用的開(kāi)銷(xiāo)
基本知識(shí)
在任何語(yǔ)言中調(diào)用一個(gè)函數(shù)都是有代價(jià)的。將參數(shù)編入寄存器或堆棧(取決于ABI),并在返回時(shí)反轉(zhuǎn)這一過(guò)程,這些都是開(kāi)銷(xiāo)。
調(diào)用一個(gè)函數(shù)需要將程序計(jì)數(shù)器從指令流中的一個(gè)點(diǎn)跳到另一個(gè)點(diǎn),這可能會(huì)導(dǎo)致流水線停滯。一旦進(jìn)入函數(shù),通常需要一些前言來(lái)為函數(shù)的執(zhí)行準(zhǔn)備一個(gè)新的堆??蚣?,在返回調(diào)用者之前,還需要一個(gè)類(lèi)似的尾聲來(lái)退掉這個(gè)框架。
Go 中的開(kāi)銷(xiāo)
在 Go 中,一個(gè)函數(shù)的調(diào)用需要額外的成本來(lái)支持動(dòng)態(tài)堆棧的增長(zhǎng)。在進(jìn)入時(shí),goroutine 可用的堆棧空間的數(shù)量與函數(shù)所需的數(shù)量進(jìn)行比較。
如果可用的堆棧空間不足,序言就會(huì)跳轉(zhuǎn)到運(yùn)行時(shí)邏輯,通過(guò)將堆棧復(fù)制到一個(gè)新的、更大的位置來(lái)增加堆棧。
一旦這樣做了,運(yùn)行時(shí)就會(huì)跳回到原始函數(shù)的起點(diǎn),再次進(jìn)行堆棧檢查,現(xiàn)在通過(guò)了,然后繼續(xù)調(diào)用。通過(guò)這種方式,goroutines可以從一個(gè)小的堆棧分配開(kāi)始,只有在需要時(shí)才會(huì)增加。
這種檢查很便宜,只需要幾條指令,而且由于goroutine的堆棧以幾何級(jí)數(shù)增長(zhǎng),檢查很少失敗。因此,現(xiàn)代處理器中的分支預(yù)測(cè)單元可以通過(guò)假設(shè)堆棧檢查總是成功來(lái)隱藏堆棧檢查的成本。在處理器錯(cuò)誤預(yù)測(cè)堆棧檢查并不得不丟棄它在投機(jī)執(zhí)行時(shí)所做的工作的情況下,與運(yùn)行時(shí)增長(zhǎng)goroutine堆棧所需的工作成本相比,管道停滯的成本相對(duì)較小。
Go 里的優(yōu)化
雖然每個(gè)函數(shù)調(diào)用的通用組件和 Go 特定組件的開(kāi)銷(xiāo)被使用投機(jī)執(zhí)行技術(shù)的現(xiàn)代處理器很好地優(yōu)化了,但這些開(kāi)銷(xiāo)不能完全消除,因此每個(gè)函數(shù)調(diào)用都帶有性能成本,超過(guò)了執(zhí)行有用工作的時(shí)間。由于函數(shù)調(diào)用的開(kāi)銷(xiāo)是固定的,較小的函數(shù)相對(duì)于較大的函數(shù)要付出更大的代價(jià),因?yàn)樗鼈兠看握{(diào)用的有用工作往往較少。
因此,消除這些開(kāi)銷(xiāo)的解決方案必須是消除函數(shù)調(diào)用本身,Go 編譯器在某些條件下通過(guò)用函數(shù)的內(nèi)容替換對(duì)函數(shù)的調(diào)用來(lái)做到這一點(diǎn)。這被稱(chēng)為內(nèi)聯(lián),因?yàn)樗购瘮?shù)的主體與它的調(diào)用者保持一致。
改善優(yōu)化的機(jī)會(huì)
Cliff Click 博士將內(nèi)聯(lián)描述為現(xiàn)代編譯器進(jìn)行的優(yōu)化,因?yàn)樗浅A總鞑ズ退来a消除等優(yōu)化的基礎(chǔ)。
實(shí)際上,內(nèi)聯(lián)允許編譯器看得更遠(yuǎn),允許它在特定函數(shù)被調(diào)用的情況下,觀察到可以進(jìn)一步簡(jiǎn)化或完全消除的邏輯。
由于內(nèi)聯(lián)可以遞歸應(yīng)用,優(yōu)化決策不僅可以在每個(gè)單獨(dú)的函數(shù)的上下文中做出,還可以應(yīng)用于調(diào)用路徑中的函數(shù)鏈。
進(jìn)行內(nèi)聯(lián)優(yōu)化
不允許內(nèi)聯(lián)
內(nèi)聯(lián)的效果可以通過(guò)這個(gè)小例子來(lái)證明:
package main 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 }
運(yùn)行這個(gè)基準(zhǔn)可以得到以下結(jié)果:
% go test -bench=.
BenchmarkMax-4 530687617 2.24 ns/op
從執(zhí)行結(jié)果來(lái)看,max(-1, i)
的成本大約是 2.24ns,感覺(jué)性能不錯(cuò)。
允許內(nèi)聯(lián)
現(xiàn)在讓我們?nèi)サ?nbsp;//go:noinline pragma
的語(yǔ)句,再看看不允許內(nèi)聯(lián)的情況下,性能是否會(huì)改變。
如下結(jié)果:
% go test -bench=.
BenchmarkMax-4 1000000000 0.514 ns/op
兩個(gè)結(jié)果對(duì)比一看,2.24ns 和 0.51ns。差距至少一倍以上,根據(jù) benchstat 的建議,內(nèi)聯(lián)情況下,性能提高了 78%。
如下結(jié)果:
% benchstat {old,new}.txt
name old time/op new time/op delta
Max-4 2.21ns ± 1% 0.49ns ± 6% -77.96% (p=0.000 n=18+19)
這些改進(jìn)從何而來(lái)?
首先,取消函數(shù)調(diào)用和相關(guān)的前導(dǎo)動(dòng)作是主要的改進(jìn)貢獻(xiàn)者。其將 max 函數(shù)的內(nèi)容拉到它的調(diào)用者中,減少了處理器執(zhí)行的指令數(shù)量,并消除了幾個(gè)分支。
現(xiàn)在 max 函數(shù)的內(nèi)容對(duì)編譯器來(lái)說(shuō)是可見(jiàn)的,當(dāng)它優(yōu)化 BenchmarkMax 時(shí),它可以做一些額外的改進(jìn)。
考慮到一旦 max 被內(nèi)聯(lián),BenchmarkMax 的主體對(duì)編譯器而言就會(huì)有所改變,與用戶端看到的并不一樣。
如下代碼:
func BenchmarkMax(b *testing.B) { var r int for i := 0; i < b.N; i++ { if -1 > i { r = -1 } else { r = i } } Result = r }
再次運(yùn)行基準(zhǔn)測(cè)試,我們看到我們手動(dòng)內(nèi)聯(lián)的版本與編譯器內(nèi)聯(lián)的版本表現(xiàn)一樣好。
如下結(jié)果:
% benchstat {old,new}.txt
name old time/op new time/op delta
Max-4 2.21ns ± 1% 0.48ns ± 3% -78.14% (p=0.000 n=18+18)
現(xiàn)在,編譯器可以獲得 max 內(nèi)聯(lián)到 BenchmarkMax 的結(jié)果,它可以應(yīng)用以前不可能的優(yōu)化方法。
例如:編譯器注意到 i 被初始化為 0,并且只被遞增,所以任何與 i 的比較都可以假定 i 永遠(yuǎn)不會(huì)是負(fù)數(shù)。因此,條件 -1 > i
將永遠(yuǎn)不會(huì)為真。
在證明了 -1 > i
永遠(yuǎn)不會(huì)為真之后,編譯器可以將代碼簡(jiǎn)化為:
func BenchmarkMax(b *testing.B) { var r int for i := 0; i < b.N; i++ { if false { // 注意已為 false r = -1 } else { r = i } } Result = r }
并且由于該分支現(xiàn)在是一個(gè)常數(shù),編譯器可以消除無(wú)法到達(dá)的路徑,只留下如下代碼:
func BenchmarkMax(b *testing.B) { var r int for i := 0; i < b.N; i++ { r = i } Result = r }
通過(guò)內(nèi)聯(lián)和它所釋放的優(yōu)化,編譯器已經(jīng)將表達(dá)式 r = max(-1, i)
簡(jiǎn)化為 r = i
。
這個(gè)例子非常不錯(cuò),很好的體現(xiàn)了內(nèi)聯(lián)的優(yōu)化過(guò)程和性能提升的緣由。
內(nèi)聯(lián)的限制
在這篇文章中,討論了所謂的葉子內(nèi)聯(lián):將調(diào)用棧底部的一個(gè)函數(shù)內(nèi)聯(lián)到其直接調(diào)用者中的行為。
內(nèi)聯(lián)是一個(gè)遞歸的過(guò)程,一旦一個(gè)函數(shù)被內(nèi)聯(lián)到它的調(diào)用者中,編譯器就可能將產(chǎn)生的代碼內(nèi)聯(lián)到它的調(diào)用者中,依此類(lèi)推。
例如如下代碼:
func BenchmarkMaxMaxMax(b *testing.B) { var r int for i := 0; i < b.N; i++ { r = max(max(-1, i), max(0, i)) } Result = r }
該運(yùn)行速度將會(huì)和前面的例子一樣快,因?yàn)榫幾g器能夠反復(fù)應(yīng)用上面的優(yōu)化,將代碼減少到相同的 r = i
表達(dá)式。
總結(jié)
這篇文章針對(duì)內(nèi)聯(lián)進(jìn)行了基本的概念介紹和分析,并且通過(guò) Go 的例子進(jìn)行了一步步的剖析,讓大家對(duì)真實(shí)案例有了一個(gè)更貼切的理解。
Go 編譯器的優(yōu)化總是無(wú)處不在的。
到此這篇關(guān)于Go 內(nèi)聯(lián)優(yōu)化讓程序員愛(ài)不釋手的文章就介紹到這了,更多相關(guān)Go 內(nèi)聯(lián)優(yōu)化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
通過(guò)Golang實(shí)現(xiàn)無(wú)頭瀏覽器截圖
在Web開(kāi)發(fā)中,有時(shí)需要對(duì)網(wǎng)頁(yè)進(jìn)行截圖,以便進(jìn)行頁(yè)面預(yù)覽、測(cè)試等操作,本文為大家整理了Golang實(shí)現(xiàn)無(wú)頭瀏覽器的截圖的方法,感興趣的可以了解一下2023-05-05Golang監(jiān)聽(tīng)日志文件并發(fā)送到kafka中
這篇文章主要介紹了Golang監(jiān)聽(tīng)日志文件并發(fā)送到kafka中,日志收集項(xiàng)目的準(zhǔn)備中,本文主要講的是利用golang的tail庫(kù),監(jiān)聽(tīng)日志文件的變動(dòng),將日志信息發(fā)送到kafka中?,需要的朋友可以參考一下2022-04-04Go語(yǔ)言數(shù)據(jù)結(jié)構(gòu)之選擇排序示例詳解
這篇文章主要為大家介紹了Go語(yǔ)言數(shù)據(jù)結(jié)構(gòu)之選擇排序示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08golang http 連接超時(shí)和傳輸超時(shí)的例子
今天小編就為大家分享一篇golang http 連接超時(shí)和傳輸超時(shí)的例子,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-07-07使用Go語(yǔ)言實(shí)現(xiàn)Yaml編碼和解碼的方法詳解
在這篇文章中,我們將介紹如何使用Go語(yǔ)言編寫(xiě)代碼來(lái)實(shí)現(xiàn)Yaml編碼和解碼,文中有詳細(xì)的代碼示例供大家參考,對(duì)大家的學(xué)習(xí)和工作有一定的幫助,需要的朋友可以參考下2023-11-11