The Go Blog

Go 1.7バイナリの小型化

デビッド・クローショー
2016年8月18日

はじめに

Goはサーバーを記述するために設計されました。今日、最も広く使用されている方法であり、その結果、ランタイムとコンパイラに関する多くの作業が、サーバーにとって重要な問題に焦点を当てています。つまり、レイテンシー、デプロイの容易さ、正確なガベージコレクション、高速な起動時間、パフォーマンスです。

Goがより多様なプログラムに使用されるにつれて、考慮すべき新しい問題が生じています。その1つがバイナリサイズです。これは長い間課題となっていましたが(イシュー #6853は2年以上前に提起されました)、Raspberry Piやモバイルデバイスなどの小型デバイスにバイナリをデプロイするためにGoを使用することへの関心が高まっているため、Go 1.7リリースで注目を集めることになりました。

Go 1.7で行われた作業

Go 1.7における3つの重要な変更がバイナリサイズに影響します。

1つ目は、このリリースでAMD64向けに有効になった新しいSSAバックエンドです。SSAの主な動機はパフォーマンスの向上でしたが、生成されるコードの品質向上は、より小さいコードサイズにもつながりました。SSAバックエンドはGoバイナリを約5%削減します。Go 1.8でARMやMIPSのようなよりRISCに近いアーキテクチャがSSAに変換された際には、より大きな削減が期待されます。

2つ目の変更は、メソッドの剪定です。1.6までは、すべての使用される型のすべてのメソッドが、たとえ一部のメソッドが一度も呼び出されなかったとしても保持されていました。これは、それらがインターフェースを介して呼び出される可能性があったり、reflectパッケージを使用して動的に呼び出される可能性があったためです。現在、コンパイラはインターフェースと一致しないエクスポートされていないメソッドを破棄します。同様に、リンカは、プログラムのどこでも対応するリフレクション機能が使用されていない場合、リフレクションを介してのみアクセス可能な他のエクスポートされたメソッドを破棄できます。この変更により、バイナリは5〜20%削減されます。

3番目の変更は、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!")
}

デバッグ情報なしでコンパイルすると、静的にリンクされたバイナリは1メガバイト未満になります。

このサイクルでテストに使用された大規模なプロダクションプログラムであるjujudは、94MBから67MBになりました。

位置独立バイナリは50%小さくなりました。

位置独立実行可能ファイル(PIE)では、読み取り専用データセクション内のポインターには動的再配置が必要です。型情報の新しい形式はポインターをセクションオフセットに置き換えるため、ポインターあたり28バイトを節約します。

デバッグ情報が削除された位置独立実行可能ファイルは、モバイル開発者にとって特に重要です。これは、電話に出荷されるプログラムの種類だからです。大きなダウンロードはユーザーエクスペリエンスを悪化させるため、ここでの削減は良いニュースです。

今後の作業

ランタイム型情報へのいくつかの変更はGo 1.7のフリーズには間に合いませんでしたが、うまくいけば1.8に組み込まれ、プログラム、特に位置独立プログラムをさらに縮小するでしょう。

これらの変更はすべて保守的であり、ビルド時間、起動時間、全体的な実行時間、メモリ使用量を増やすことなくバイナリサイズを削減します。バイナリサイズを削減するためにもっと根本的な措置を講じることもできます。実行可能ファイルを圧縮するためのupxツールは、起動時間の増加とメモリ使用量の増加を犠牲にして、バイナリをさらに50%削減します。非常に小さなシステム(キーホルダーに収まるようなもの)の場合、リフレクションなしのGoバージョンを構築することもできますが、そのような制限された言語が十分に有用であるかは不明です。ランタイムの一部のアルゴリズムでは、1キロバイトが重要となる場合に、より遅いがよりコンパクトな実装を使用することもできます。これらすべては、後の開発サイクルでのさらなる研究を必要とします。

Go 1.7バイナリの小型化にご協力いただいた多くの貢献者の皆様、ありがとうございました!

次の記事:サブテストとサブベンチマークの使用
前の記事:Go 1.7がリリースされました
ブログインデックス