Go 部落格
Go 1.7 二進位制檔案更小
引言
Go 的設計初衷是為了編寫伺服器。如今它最廣泛的用途也是如此,因此執行時和編譯器的大量工作都集中在對伺服器至關重要的方面:延遲、易於部署、精確的垃圾回收、快速啟動時間和效能。
隨著 Go 被用於更多種類的程式,需要考慮新的問題。其中之一就是二進位制檔案的大小。這個問題已經存在了很長時間(兩年多前就提交了 issue #6853),但隨著在部署到較小裝置(如樹莓派或移動裝置)上使用 Go 部署二進位制檔案的興趣日益濃厚,Go 1.7 版本對此給予了一些關注。
Go 1.7 中的工作
Go 1.7 中影響二進位制檔案大小的三個重要變化。
第一個是本次釋出中為 AMD64 啟用的新 SSA 後端。雖然 SSA 的主要動機是提高效能,但生成的程式碼也更小。SSA 後端將 Go 二進位制檔案縮小了約 5%。我們預計當 ARM 和 MIPS 等更精簡的 RISC 架構在 Go 1.8 中轉換為 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!")
}
當編譯時移除除錯資訊後,靜態連結的二進位制檔案現在小於 1MB。

本次測試周期中用於測試的一個大型生產程式 `jujud`,從 94MB 降至 67MB。
位置無關二進位制檔案縮小了 50%。
在位置無關可執行檔案 (PIE) 中,只讀資料段中的指標需要動態重定位。由於型別資訊的新格式用節偏移量替換了指標,因此每個指標節省了 28 位元組。
移除除錯資訊的位置無關可執行檔案對移動開發者尤為重要,因為這是 shipped 到手機上的程式型別。下載量過大對使用者體驗不好,因此這裡的減小是一個好訊息。
未來工作
對執行時型別資訊的幾項更改由於太晚而未能趕上 Go 1.7 的凍結,但希望能在 1.8 中實現,這將進一步減小程式的大小,尤其是位置無關程式。
所有這些更改都是保守的,它們減小了二進位制檔案的大小,而沒有增加構建時間、啟動時間、整體執行時間或記憶體使用量。我們可以採取更激進的措施來減小二進位制檔案的大小:用於壓縮可執行檔案的 upx 工具可以將二進位制檔案再縮小 50%,但代價是增加啟動時間和可能增加記憶體使用量。對於極小的系統(比如那些可能掛在鑰匙鏈上的裝置),我們可以構建一個不包含反射的 Go 版本,儘管不清楚這樣受限的語言是否足夠有用。對於執行時中的某些演算法,當每一千位元組都很重要時,我們可以使用更慢但更緊湊的實現。所有這些都需要在後續的開發週期中進行更多研究。
感謝許多為使 Go 1.7 二進位制檔案更小做出貢獻的貢獻者!
下一篇文章: 使用子測試和子基準測試
上一篇文章: Go 1.7 釋出
部落格索引