Go 部落格

檔案大小較小的 Go 1.7 二進位檔

David Crawshaw
2016 年 8 月 18 日

簡介

Go 設計用於撰寫伺服器。這是現今最廣泛的用途,也因此執行時期和編譯器上的許多工作都專注於伺服器相關的問題:延遲、易於部署、精確垃圾收集、快速啟動時間、效能。

隨著 Go 用於越來越多元的程式,必須考量的新問題也隨之而來。其中之一就是二進位檔大小。這問題已經存在很長一段時間(#6853 問題於兩年前提出),但對於將 Go 用於較小型裝置(例如 Raspberry Pi 或行動裝置)部署二進位檔案的興趣日漸增加,意味著 Go 1.7 版本中對此有了一些改善。

Go 1.7 中完成的工作

Go 1.7 中的三項重大變更有助於改善二進位檔大小。

第一個是這個版本中為 AMD64 啟用的新的 SSA 後端。雖然 SSA 的主要目的在於提升效能,但產生的程式碼也較小。SSA 後端讓 Go 二進制程式縮小約 5%。當這些後端在 Go 1.8 中轉換成 SSA 時,我們預期像 ARM 和 MIPS 等更類 RISC 架構會有更高的獲益。

第二個變更就是方法修剪。在 1.6 之前,所有已使用類型的所有方法都會保留,即使其中某些方法從未呼叫過。這是因為方法可能透過介面呼叫,或使用 reflect 套件進行動態呼叫。現在,編譯器會捨棄任何不符合介面的非匯出方法。同樣地,如果在任何地方未曾使用相關的 reflect 特性,連結器也可以捨棄其他匯出方法,這些方法只能透過反映存取。這樣的變更讓二進制程式縮小 5–20% 左右。

第三個變更是由 reflect 套件用於執行時期類型的資訊更為精簡的格式。編碼格式最初是為了讓執行時期和 reflect 套件中的解碼器盡可能簡單。雖然讓程式碼更難閱讀,但是我們可以在不影響 Go 程式執行時期效能的狀況下壓縮格式。新的格式讓 Go 二進制程式進一步縮小 5–15%。針對 Android 建置的函式庫和針對 iOS 建置的檔案壓縮得更多,這是因為新的格式含有較少的指標,而每個指標都需要位置獨立程式碼中的動態重定位。

此外,也有許多較小的改進,例如進步的介面資料配置、更好的靜態資料配置和簡化的相依性。舉例來說,HTTP 客戶端不再連結整個 HTTP 伺服器。變更的完整清單可以在議題 #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 個位元組。

清除偵錯資訊的位置獨立可執行檔對於行動裝置開發人員來說非常重要,因為這是傳輸到手機的程式類型。如果檔案下載太大會讓使用者體驗很差,因此縮小檔案是好消息。

後續處理

對執行時期類型資訊的某些變更為 Go 1.7 凍結時程太晚,但希望可以加入 1.8,讓程式進一步縮小,尤其是位置獨立程式。

這些變更都是保守的,在不增加建置時間、啟動時間、整體執行時間或記憶體用量的同時,就能減少二進制大小。我們可以採取更激進的步驟來縮減二進制大小:用於壓縮執行檔的 upx 工具能夠再縮減二進制大小 50%,但代價是增加啟動時間並可能增加記憶體用量。對於極為小的系統(可能存放在鑰匙圈中的那種),我們可以建置一個沒有反射功能的 Go 版本,不過目前尚不清楚這樣受限的語言是否足夠好用。對於執行階段中的一些演算法,當每一個位元組都很重要的時候,我們可以使用較慢但體積較小的實作。這一切都需要在後續的開發週期中進行進一步研究。

感謝眾多協助讓 Go 1.7 二進制較小的貢獻者!

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