翻譯原文鏈接? ?轉(zhuǎn)帖/轉(zhuǎn)載請注明出處
英文原文鏈接? ?發(fā)表于2014/06/07
函數(shù)調(diào)用不是免費的
一個函數(shù)調(diào)用有三個步驟寂汇。創(chuàng)建一個新的堆棧框(stack frame)并把調(diào)用者的詳細信息記錄下來捣染。把任何會被被調(diào)用函數(shù)用到的寄存器內(nèi)容保存到堆棧骄瓣。計算被調(diào)用函數(shù)的地址,并執(zhí)行跳轉(zhuǎn)指令到那個新的地址耍攘。
因為函數(shù)調(diào)用是頻繁操作累贤,CPU的設(shè)計者花費了很多精力來優(yōu)化這個過程,但他們不可能消除所有的開銷少漆。
根據(jù)被調(diào)用函數(shù)的功能臼膏,這個調(diào)用開銷可能是可以忽略不計的,也可能是非常顯著的示损。有一個降低調(diào)用開銷的優(yōu)化技術(shù)叫內(nèi)聯(lián)(inlining)渗磅。
Go語言編譯器通過把被調(diào)用函數(shù)代碼當作調(diào)用者代碼的一部分來實現(xiàn)內(nèi)聯(lián)。內(nèi)聯(lián)也是有代價的检访。它會增加編譯出來的二進制可執(zhí)行文件的大小始鱼。只有在調(diào)用函數(shù)的開銷占到被調(diào)用函數(shù)本身的工作量很大一部分的時候,內(nèi)聯(lián)才有意義脆贵。所以只有簡單的函數(shù)才被考慮啟用內(nèi)聯(lián)医清。調(diào)用函數(shù)的開銷往往不占復(fù)雜函數(shù)的大頭,所以他們也就不會被內(nèi)聯(lián)卖氨。
上面這個例子展示了函數(shù)Double對util.Max的調(diào)用会烙。為了降低調(diào)用util.Max的成本负懦,編譯器會把util.Max內(nèi)聯(lián)到Double函數(shù)里,產(chǎn)生如下內(nèi)容:
內(nèi)聯(lián)之后柏腻,util.Max將不會被調(diào)用纸厉,但是Double的行為并沒有改變。內(nèi)聯(lián)并不是Go語言獨有的五嫂。幾乎所有編譯的或者即時編譯(JITed)的語言會提供這項優(yōu)化颗品。那么Go語言里的內(nèi)聯(lián)是怎么工作的呢?
Go語言的實現(xiàn)非常簡單沃缘。當一個包(package)被編譯的時候躯枢,任何適合內(nèi)聯(lián)的小函數(shù)都被標記并且按正常情況編譯。然后將源代碼和編譯后的二進制同時保存下來槐臀。
上面的圖片顯示了util.a的內(nèi)容闺金。源代碼被做了稍微的改動以方便編譯器的快速處理。當編譯器編譯Double的時候峰档,它會發(fā)現(xiàn)util.Max是可以內(nèi)聯(lián)的并且util.Max的源代碼也存在。這時編譯器會插入原函數(shù)的源代碼寨昙,而不是插入一個util.Max的調(diào)用讥巡。
保存源代碼還使得其它優(yōu)化成為可能。
比如上面這個例子舔哪,雖然Test函數(shù)總是返回false欢顷,Expensive在執(zhí)行它之前是無法知道的。但是當Test被內(nèi)聯(lián)的時候捉蚤,我們就得到了如下的代碼:
這樣編譯器就能知道那塊代碼是不會被執(zhí)行到的抬驴。
這樣不僅節(jié)省了調(diào)用Test函數(shù)的開銷,它還節(jié)省了編譯任何不會被執(zhí)行的代碼缆巧。Go編譯器能夠自動在多個文件或者包(package)之間實現(xiàn)函數(shù)內(nèi)聯(lián)布持。如果某些代碼調(diào)用了來自標準庫的可內(nèi)聯(lián)函數(shù),Go編譯器同樣可以將這些函數(shù)內(nèi)聯(lián)進來陕悬。