Golang 隱藏技能 -- 編譯指令

類似C++中的 #pragma pack(2),Golang中也有一些編譯指令汇陆。它們的實(shí)現(xiàn)方式是一些特殊的注釋毡代。

警告一下教寂!

編譯指令不是語言的一部分孝宗。它們可能是編譯器實(shí)現(xiàn)的因妇,編程規(guī)范中也沒有對它們的描述(更正一下婚被,現(xiàn)在有一部分指令的描述了https://golang.org/cmd/compile/
)址芯。
語法:
//go:directive
編譯指令的語法是一行特殊的注釋谷炸,關(guān)鍵字//和go之間沒有空格旬陡。

//go:noescape
func NewBook() (*Book) {
        b := Book{ Mice: 12, Men: 9 }
        return &b
}

這段代碼在C/C++中這樣做描孟,返回的是不可用的地址匿醒,顯然是要出問題的廉羔。在go中是可以的蜜另。因?yàn)樘右莘治鼍俟澹琤將會被分配在堆上此迅。

逃逸分析:

逃逸分析可以識別生命周期超出變量聲明函數(shù)的生命周期,并將變量從棧的分配上移動到堆中 Technically we say that b escapes to the heap.

func BuildLibrary() {
        b := Book{Mice: 99: Men: 3}
        AddToCollection(&b)
}

問題: b逃逸到了堆中鲁猩?
這取決于AddToCollection 對b做了什么

func AddToCollection(b *Book) {
        b.Classification = "fiction"
}

逃逸分析發(fā)現(xiàn)AddToCollection并沒有將*book繼續(xù)傳遞廓握,所以此時(shí)b會被分配在棧上隙券。

但是娱仔,如果AddToCollection做了這樣的操作:

var AvailableForLoan [] *Book
func AddToCollection(b * Book){
        AvailableForLoan = append(AvailableForLoan,b)
}

AddToCollection中將bappend到了一個(gè)生命周期更長的slice中盹憎,所以b必須被分配在堆上以保證的生命周期大于AddToCollection和BuildLibrary的脚乡。逃逸分析必須知道AddToCollection對b做了什么,調(diào)用了什么func 等等捡遍,以了解值是應(yīng)該分配在棧上還是堆上画株。這是逃逸分析的本質(zhì)谓传。

再看另一個(gè)例子:

os.File.Read
f, _ := os.Open("/tmp/foo")
buf := make([]byte, 4096)
n, _ := f.Read(buf)

我們打開一個(gè)文件续挟,創(chuàng)建一個(gè)buf诗祸,然后讀取數(shù)據(jù)到buf中直颅。此時(shí)buf是在棧上還是堆上功偿?
如上節(jié)所述械荷,這取決于Read內(nèi)部發(fā)生的事情征堪。os.Read通過幾層調(diào)用調(diào)到了syscall.Read佃蚜,然后又調(diào)到了syscall.Syscall來進(jìn)行操作系統(tǒng)調(diào)用谐算。而syscall.Syscall是在匯編中實(shí)現(xiàn)的洲脂,所以Go中的編譯器無法“看到”該函數(shù)的實(shí)現(xiàn)恐锦,因此無法判斷傳遞的值是否為escape一铅。由于編譯器不能知道是否需要escape所以潘飘,buf只能被判定為escape卜录。

回到//go:noescape編譯指令來
假設(shè)我們要用匯編寫一段glue code,類似bytes完域,md5凯傲,syscall 包冰单。
我們傳遞的值都會被分配在堆上诫欠。即使我們知道這樣做沒有必要荒叼。

package bytes
//go:noescape
// IndexByte returns the index of the first instance of c in s,
// or -1 if c is not present in s.
func IndexByte(s []byte, c byte) int // ../runtime/asm_$GOARCH.s

這就是//go:noescape的意義了,這個(gè)指令告訴編譯器 下面的func沒有任何參數(shù)escape嫁乘。編譯器將會跳過對func參數(shù)的檢查蜓斧。
//go:escape只能用于前置聲明(即 指令下的第一個(gè)func會受指令影響)
不過要格外關(guān)注的是挎春,這個(gè)命令會使代碼跳過編譯器的檢查直奋。如果弄錯(cuò)了就會破壞內(nèi)存帮碰,且沒有工具能發(fā)現(xiàn)這一點(diǎn)丰涉。

//go:norace

norace指令的用法和noescape一樣一死。Norace指令可以使編譯器跳過競爭檢測
鑒于競爭檢測器沒有已知的誤報(bào)投慈,應(yīng)該沒有理由將函數(shù)從其范圍中排除。

//go:nosplit

我們都知道goroutine的棧是可以動態(tài)自增的。Runtime會追蹤每個(gè)stack的使用情況扁誓。在運(yùn)行函數(shù)之前會進(jìn)行檢查以確保有足夠的椈雀遥空間來運(yùn)行該函數(shù)寿谴。如果不夠讶泰,代碼先進(jìn)入runtime擴(kuò)充stack。
但是有時(shí)這種開銷是不可接受的(偶爾也是不安全的)

//go:nosplit指令禁止stack拆分惠桃。但是這會導(dǎo)致一個(gè)問題辜王,如果你的堆棧耗盡呐馆,會發(fā)生什么//go:nosplit汹来?編譯器必須確保運(yùn)行函數(shù)是安全的,不能因?yàn)楸苊饬藯z查的開銷就讓函數(shù)使用比被允許空間更多的內(nèi)存摔桦。因?yàn)檫@樣做的話肯定會破壞其他goroutine的內(nèi)存空間邻耕。

為此兄世,編譯器維護(hù)一個(gè)名為redzone的緩沖區(qū)熙兔,一個(gè)768字節(jié)的住涉,分配在每個(gè)goroutines的堆椨呱框架底部媳握,保證可用蛾找。
編譯器會跟蹤每個(gè)函數(shù)的堆棧要求打毛。當(dāng)它遇到一個(gè)nosplit函數(shù)時(shí)幻枉,它會累積該函數(shù)對redzone的堆棧分配熬甫。通過這種方式,nosplit函數(shù)可以安全地對redzone緩沖區(qū)執(zhí)行覆旱,同時(shí)避免在不方便的時(shí)候堆棧增長。
(關(guān)于redzone的詳細(xì)描述 會單起一文)

package main
type T [256]byte // a large stack allocated type

//go:nosplit
func A(t T) {
        B(t)
}

//go:nosplit
func B(t T) {
        C(t)
}

//go:nosplit
func C(t T) {
        D(t)
}

//go:nosplit
//go:noinline
func D(t T) {}

func main() {
        var t T
        A(t)
}
# command-line-arguments
main.C: nosplit stack overflow
    744 assumed on entry to main.A (nosplit)
    480 after main.A (nosplit) uses 264
    472 on entry to main.B (nosplit)
    208 after main.B (nosplit) uses 264
    200 on entry to main.C (nosplit)
    -64 after main.C (nosplit) uses 264

上面這段程序嘗試使用nosplit噪沙,但是不會編譯,因?yàn)榫幾g器檢測到redzone會耗盡

我們是否應(yīng)該在代碼中使用//go:nosplit局义?可以用萄唇,但是沒有必要。小函數(shù)從這種優(yōu)化中獲取的收益要比內(nèi)聯(lián)帶來的收益小四敞。上面的示例中用了//go:noinline禁止內(nèi)聯(lián)忿危。否則會檢測到D()什么也沒做,因此編譯器會優(yōu)化掉整個(gè)調(diào)用樹努释。
在所有指令中//go:nosplit是最安全的伐蒂。因?yàn)樗鼤诰幾g時(shí)被發(fā)現(xiàn)。并且不會影響程序正確性缕减,只會影響性能。

//go:noinlie

noinlie顧名思義裹芝,告訴編譯器不要inline嫂易。是否在我們的代碼中應(yīng)該這樣做呢怜械?我建議是不要用noinline指令的峡扩。

最后:

Go支持更多的編譯指令,但不在本文討論范圍
+build是Go tool而不是編譯器實(shí)現(xiàn)有额,為了過濾傳遞給編譯器的build或test文件。
編譯指令沒有出現(xiàn)在官方文檔中萤衰,編譯指令使用是有風(fēng)險(xiǎn)的。如果需要使用到這些編譯指令洒擦,請先翻閱相關(guān)指令在Golang源碼中的使用方法椿争。

參考文獻(xiàn):gos-hidden-pragmas

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市熟嫩,隨后出現(xiàn)的幾起案子秦踪,更是在濱河造成了極大的恐慌,老刑警劉巖掸茅,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異昧狮,居然都是意外死亡景馁,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門逗鸣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來合住,“玉大人绰精,你說我怎么就攤上這事×钠#” “怎么了茬底?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長获洲。 經(jīng)常有香客問我,道長殿如,這世上最難降的妖魔是什么贡珊? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮涉馁,結(jié)果婚禮上门岔,老公的妹妹穿的比我還像新娘。我一直安慰自己烤送,他們只是感情好寒随,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著帮坚,像睡著了一般妻往。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上试和,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天讯泣,我揣著相機(jī)與錄音,去河邊找鬼阅悍。 笑死好渠,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的节视。 我是一名探鬼主播拳锚,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼寻行!你這毒婦竟也來了霍掺?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤寡痰,失蹤者是張志新(化名)和其女友劉穎抗楔,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拦坠,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡连躏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了贞滨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片入热。...
    茶點(diǎn)故事閱讀 39,731評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拍棕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出勺良,到底是詐尸還是另有隱情绰播,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布尚困,位于F島的核電站蠢箩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏事甜。R本人自食惡果不足惜谬泌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望逻谦。 院中可真熱鬧掌实,春花似錦、人聲如沸邦马。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽滋将。三九已至邻悬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間耕渴,已是汗流浹背拘悦。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留橱脸,地道東北人础米。 一個(gè)月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像添诉,于是被迫代替她去往敵國和親屁桑。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容