剖析使Go語言高效的5個特性(2/5): 函數調用不是免費的

翻譯原文連接 轉帖/轉載請註明出處函數

英文原文連接 發表於2014/06/07post

函數調用不是免費的

一個函數調用有三個步驟。建立一個新的堆棧框(stack frame)並把調用者的詳細信息記錄下來。把任何會被被調用函數用到的寄存器內容保存到堆棧。計算被調用函數的地址,並執行跳轉指令到那個新的地址。優化

圖片描述

由於函數調用是頻繁操做,CPU的設計者花費了不少精力來優化這個過程,但他們不可能消除全部的開銷。spa

圖片描述

根據被調用函數的功能,這個調用開銷多是能夠忽略不計的,也多是很是顯著的。有一個下降調用開銷的優化技術叫內聯(inlining).net

圖片描述

Go語言編譯器經過把被調用函數代碼看成調用者代碼的一部分來實現內聯。內聯也是有代價的。它會增長編譯出來的二進制可執行文件的大小。只有在調用函數的開銷佔到被調用函數自己的工做量很大一部分的時候,內聯纔有意義。因此只有簡單的函數才被考慮啓用內聯。調用函數的開銷每每不佔複雜函數的大頭,因此他們也就不會被內聯。翻譯

圖片描述

上面這個例子展現了函數Double對util.Max的調用。爲了下降調用util.Max的成本,編譯器會把util.Max內聯到Double函數裏,產生以下內容:設計

圖片描述

內聯以後,util.Max將不會被調用,可是Double的行爲並無改變。內聯並非Go語言獨有的。幾乎全部編譯的或者即時編譯(JITed)的語言會提供這項優化。那麼Go語言裏的內聯是怎麼工做的呢?code

Go語言的實現很是簡單。當一個包(package)被編譯的時候,任何適合內聯的小函數都被標記而且按正常狀況編譯。而後將源代碼和編譯後的二進制同時保存下來。圖片

圖片描述

上面的圖片顯示了util.a的內容。源代碼被作了稍微的改動以方便編譯器的快速處理。當編譯器編譯Double的時候,它會發現util.Max是能夠內聯的而且util.Max的源代碼也存在。這時編譯器會插入原函數的源代碼,而不是插入一個util.Max的調用。get

保存源代碼還使得其它優化成爲可能。

圖片描述

好比上面這個例子,雖然Test函數老是返回false,Expensive在執行它以前是沒法知道的。可是當Test被內聯的時候,咱們就獲得了以下的代碼:

圖片描述

這樣編譯器就能知道那塊代碼是不會被執行到的。

這樣不只節省了調用Test函數的開銷,它還節省了編譯任何不會被執行的代碼。Go編譯器可以自動在多個文件或者包(package)之間實現函數內聯。若是某些代碼調用了來自標準庫的可內聯函數,Go編譯器一樣能夠將這些函數內聯進來。

相關文章
相關標籤/搜索