Go 部落格

更小的 Go 1.7 二進位制檔案

David Crawshaw
2016 年 8 月 18 日

引言

Go 設計之初是為了編寫伺服器程式。這也是 Go 目前最廣泛的用途。因此,執行時和編譯器的許多工作都集中在與伺服器相關的問題上:延遲、易於部署、精確垃圾回收、快速啟動時間、效能。

隨著 Go 被用於更廣泛的程式型別,一些新問題也必須被考慮。其中之一就是二進位制檔案大小。這個問題已經被關注很久了(issue #6853 是兩年多前提交的),但是人們對將 Go 二進位制檔案部署到樹莓派或移動裝置等小型裝置上越來越感興趣,這意味著這個問題在 Go 1.7 版本中受到了關注。

Go 1.7 中完成的工作

Go 1.7 中的三個重大變化影響了二進位制檔案大小。

第一個是此版本中為 AMD64 啟用的新 SSA 後端。雖然 SSA 的主要動機是提高效能,但生成的程式碼質量更高,體積也更小。SSA 後端將 Go 二進位制檔案縮小了約 5%。我們預計,在 Go 1.8 中將 ARM 和 MIPS 等更像 RISC 的架構轉換為 SSA 後,將會獲得更大的收益。

第二個變化是方法修剪。在 1.6 之前,所有使用過的型別的所有方法都會被保留,即使其中一些方法從未被呼叫過。這是因為它們可能透過介面被呼叫,或者使用 reflect 包動態呼叫。現在編譯器會丟棄任何不匹配介面的未匯出方法。類似地,如果程式中沒有使用相應的 反射功能,連結器也可以丟棄其他匯出的方法,即那些只能透過反射訪問的方法。這個變化使二進位制檔案縮小了 5%–20%。

第三個變化是 reflect 包使用的執行時型別資訊的格式更加緊湊。最初設計這種編碼格式是為了使執行時和 reflect 包中的解碼器儘可能簡單。透過稍微降低程式碼的可讀性,我們可以壓縮格式,同時不影響 Go 程式的執行時效能。新格式使 Go 二進位制檔案進一步縮小了 5%–15%。為 Android 構建的庫和為 iOS 構建的歸檔檔案會進一步縮小,因為新格式包含的指標更少,而每個指標在位置無關程式碼中都需要動態重定位。

此外,還有許多小的改進,例如改進的介面資料佈局、更好的靜態資料佈局以及簡化的依賴關係。例如,HTTP 客戶端不再連結整個 HTTP 伺服器。更改的完整列表可以在 issue #6853 中找到。

結果

使用 Go 1.7 構建時,典型的程式,從小型演示程式到大型生產程式,體積大約縮小了 30%。

典型的 Hello World 程式從 2.3MB 變為 1.6MB

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}

在編譯時不包含除錯資訊時,靜態連結的二進位制檔案現在小於一兆位元組。

用於本次測試周期的大型生產程式 jujud 從 94MB 變為 67MB。

位置無關二進位制檔案縮小了 50%。

在位置無關可執行檔案(PIE)中,只讀資料段中的指標需要動態重定位。由於新的型別資訊格式用節偏移量代替了指標,每個指標節省了 28 位元組。

移除除錯資訊的位置無關可執行檔案對移動開發者尤其重要,因為這是傳送到手機上的程式型別。大的下載檔案會帶來糟糕的使用者體驗,所以這次的縮小是個好訊息。

未來工作

一些對執行時型別資訊的更改在 Go 1.7 凍結前已來不及納入,但有望在 1.8 版本中實現,進一步縮小程式體積,特別是位置無關的程式。

所有這些更改都是保守的,在減小二進位制檔案大小的同時,不增加構建時間、啟動時間、總體執行時間或記憶體使用。我們可以採取更激進的措施來減小二進位制檔案大小:用於壓縮可執行檔案的 upx 工具可以將二進位制檔案再縮小 50%,代價是增加啟動時間並可能增加記憶體使用。對於極小的系統(例如可能放在鑰匙鏈上的那種),我們可以構建一個不包含反射功能的 Go 版本,儘管這種受限的語言是否足夠有用尚不清楚。對於執行時中的某些演算法,當每一千位元組都很重要時,我們可以使用較慢但更緊湊的實現。所有這些都需要在後續開發週期中進行更多研究。

感謝為減小 Go 1.7 二進位制檔案體積做出貢獻的許多貢獻者們!

下一篇文章:使用子測試和子基準測試
上一篇文章:Go 1.7 已釋出
部落格索引